共享數據是並發程序最核心的問題之一,對於繼承了Thread類或者實現了Runnable接口的對象來說尤其重要。如果創建的對象是實現了Runable接口的類的實例,用它作為傳入參數創建多個線程對象並啟動這些線程,那么所有的線程將共享相同的屬性。也就是說,如果你在一個線程中改變了一個屬性,所有的線程都會被這個改變所影響。
在某種情況下,這個對象的屬性不需要被所有線程共享。Java並發API提供了一個干凈的機制,即線程局部變量(Thread-Local Variable),其具有很好的性能。示例如下:
import java.util.Date; import java.util.concurrent.TimeUnit; public class SafeTask implements Runnable { private static ThreadLocal<Date> startDate = new ThreadLocal<Date>(){ @Override protected Date initialValue() { return new Date(); } }; @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) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.printf("Thread Finished: %s : %s\n", Thread.currentThread().getId(), startDate.get()); } public static void main(String[] args){ SafeTask st = new SafeTask(); try { for(int i=0;i<10;i++){ Thread t = new Thread(st); t.start(); TimeUnit.SECONDS.sleep(2); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
線程局部變量分別為每個線程存儲了各自的屬性值,並提供給每個線程使用。你可以使用get()方法讀取這個值,並用set()方法設置這個值。如果線程是第一次訪問線程局部變量,線程局部變量可能還沒有為它存儲值,這個時候initialValue()方法就會被調用,並且返回當前的時間值。
線程局部變量也提供了remove()方法,用來訪問這個變量的線程刪除已經存儲的值。Java並發API包含了InheritableThreadLocal類,如果一個線程是從其他某個線程中創建的,這個類將提供繼承的值。如果一個線程A在線程局部變量已有值,當它創建其它某個線程B時,線程B的線程局部變量將跟線程A是一樣的。你可以覆蓋ChileValue()方法,這個方法用來初始化子線程在線程局部變量中的值。它使用父線程在線程局部變量中的值作為傳入參數。