1. 程式人生 > >執行緒管理(九)使用本地執行緒變數

執行緒管理(九)使用本地執行緒變數

宣告:本文是《 Java 7 Concurrency Cookbook 》的第一章, 作者: Javier Fernández González 譯者:鄭玉婷 校對:方騰飛

使用本地執行緒變數

併發應用的一個關鍵地方就是共享資料。這個對那些擴充套件Thread類或者實現Runnable介面的物件特別重要。

如果你建立一個類物件,實現Runnable介面,然後多個Thread物件使用同樣的Runnable物件,全部的執行緒都共享同樣的屬性。這意味著,如果你在一個執行緒裡改變一個屬性,全部的執行緒都會受到這個改變的影響。

有時,你希望程式裡的各個執行緒的屬性不會被共享。 Java 併發 API提供了一個很清楚的機制叫本地執行緒變數。

在這個指南中, 我們將開發一個程式,這個程式用來描述在第一段話裡的問題,和另一個程式使用本地執行緒變數機制解決這個問題。

準備

指南中的例子是使用Eclipse IDE 來實現的。如果你使用Eclipse 或者其他的IDE,例如NetBeans, 開啟並建立一個新的java專案。

怎麼做呢

按照這些步驟來實現下面的例子:

1.   首先,我們來實現一個程式含有上述的問題。

建立一個類名為 UnsafeTask 並實現 Runnable 介面。 宣告一個 private java.util.Date 屬性.

public class UnsafeTask implements Runnable{
private Date startDate;

2. 實現UnsafeTask 物件的run() 方法,此方法會初始 startDate 屬性, 把值寫入控制檯,隨機休眠一段時間,最後在寫入startDate 屬性。

@Override
public void run() {
startDate=new Date();
System.out.printf("Starting Thread: %s : %s\n",Thread. currentThread().getId(),startDate);
try {
TimeUnit.SECONDS.sleep( (int)Math.rint(Math.random()*10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread Finished: %s : %s\n",Thread. currentThread().getId(),startDate);
}

3.   現在,來實現這個有問題例子的主類。建立一個 Main  類和 main() 方法. 此方法會建立一個 UnsafeTask 類的物件,並開始3個執行緒使用這個物件,每個執行緒間休眠2秒。

public class Core {
public static void main(String[] args) {
UnsafeTask task=new UnsafeTask();
 for (int i=0; i<10; i++){
Thread thread=new Thread(task);
thread.start();
try { TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

4.   在以下的裁圖,你可以發現這個程式的執行結果。每個執行緒有著不同的開始時間,但是全部都有相同的結束時間。

5.   如在之前提到的, 我們會使用本地執行緒變數機制來解決這個問題。

6.   建立一個類名為 SafeTask a一定實現 Runnable 介面。

public class SafeTask implements Runnable {

7.   宣告 ThreadLocal<Date> 類物件。此物件有隱含實現了 initialValue()方法. 此方法會返回真實日期。

private static ThreadLocal<Date> startDate= new ThreadLocal<Date>() {
protected Date initialValue(){
return new Date();
}
};

8.   實現run()方法。它和 UnsafeClass的run() 方法功能一樣,只是改變了屬性的訪問方式。

@Override
public void run() {
  System.out.printf("Starting Thread: %s : %s\n",Thread.currentThread().getId(),startDate.get());
try {
  TimeUnit.SECONDS.sleep((int)Math.rint(Math.random()*10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread Finished: %s : %s\n",Thread.currentThread().getId(),startDate.get());
}

9.    這個例子的主類跟不安全例子一樣,把名字改成 Runnable 類。

10. 執行例子並分析不同處。

它是怎麼工作的

在下面的截圖裡,你可以看到執行緒安全模式下程式執行的結果。現在3個 Thread 物件都有他們自己的startDate 屬性值。看下圖:

本地執行緒變數為每個使用這些變數的執行緒儲存屬性值。可以用 get() 方法讀取值和使用 set() 方法改變值。 如果第一次你訪問本地執行緒變數的值,如果沒有值給當前的執行緒物件,那麼本地執行緒變數會呼叫 initialValue() 方法來設定值給執行緒並返回初始值。

更多

本地執行緒類還提供 remove() 方法,刪除儲存線上程本地變數裡的值。

Java 併發 API 包括 InheritableThreadLocal 類提供執行緒建立執行緒的值的遺傳性 。如果執行緒A有一個本地執行緒變數,然後它建立了另一個執行緒B,那麼執行緒B將有與A相同的本地執行緒變數值。 你可以覆蓋 childValue() 方法來初始子執行緒的本地執行緒變數的值。 它接收父執行緒的本地執行緒變數作為引數。