執行緒管理(九)使用本地執行緒變數
宣告:本文是《 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() 方法來初始子執行緒的本地執行緒變數的值。 它接收父執行緒的本地執行緒變數作為引數。