共享數據是並發程序最核心的問題之一,對於繼承Thread類或者實現Runnable接口的對象來說尤其重要。
如果創建的對象實現了Runnable接口的類的實例,用它作為傳入參數,並創建多個線程對象並啟動這些線程,那么所有的線程將共享相同的屬性。如果在一個線程中改變一個屬性,所有線程都會被這個改變影響。
在某種情況下,這個對象的屬性不需要被所有線程共享。JAVA提供了一個比較好的機制,即線程局部變量(Thread-Local Variable).
我們寫一個簡單的DEMO,一是具有剛才提到的問題,另一個使用線程局部變量機制解決這個問題。
1.創建一個類為UnsafeTask的類,它實現了Runnable接口。並且聲明一個java.util.Date屬性。
public class UnsafeTask implements Runnable{
private Date startDate; }
2.實現run()方法。這個方法初始化startDate屬性,並且將值打印到控制台。讓線程休眠一個隨機時間,然后再次將值打印到控制台。
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類對象,用它作為傳入參數創建10個線程對象並啟動10個線程,每個線程啟動時間間隔為2秒。
public class Main {
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.運行結果如圖
每個線程有一個不同的開始時間。他們結束時,三個線程就有相同的startDate屬性值。
5.接下來我們使用線程局部變量機制來解決上述問題。
6.創建一個SafeTask類,用以實現Runnable接口。
public class SafeTask implements Runnable{}
7.聲明一個ThreadLocal<Date>對象。這個對象是在initialValue()方法中隱式實現,返回當前日期。
private static ThreadLocal<Date> startDate=new ThreadLocal<Date>(){
protected Date initalValue(){
return new Date();
}
};
8.實現run()方法。跟上面方法實現一樣功能,但是startDate屬性方式改變了。
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.Main主類同上,只是創建並作為參數傳入的Runnable類對象不同而已。
public class Main {
public static void main(String[] args){
SafeTask task=new SafeTask();
for(int i=0;i<3;i++){
Thread thread=new Thread(task);
try{
TimeUnit.SECONDS.sleep(2);
}catch(InterruptedException e){
e.printStackTrace();
}
thread.start();
}
}
}
10.運行結果。