方式有四種:① ThreadLocal ② synchronized( ) ③ wait() 與 notify() ④ volatile
目的:都是為了解決多線程中的對同一變量的訪問沖突
ThreadLocal
. ㈠ ThreadLocal 保證不同線程擁有不同實例,相同線程一定擁有相同的實例,即為每一個使用該變量的線程提供一個該變量值的副本,每一個線程都可以獨立改變自己的副本,而不是與其它線程的副本沖突。
. ㈡優勢:提供了線程安全的共享對象
. ㈢與其它同步機制的區別:同步機制是為了同步多個線程對相同資源的并發訪問,是為了多個線程之間進行通信;而 ThreadLocal 是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源,這樣當然不需要多個線程進行同步了。
. ㈣使用技巧:需要多個線程之間進行通信:使用同步機制;如果需要隔離多個線程之間的共享沖突:推薦使用 ThreadLocal (線程安全)
volatile
.㈠volatile 修飾的成員變量在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。而且,當成員變量發生變化時,強迫線程將變化值回寫到共享內存。
.㈡優勢:這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。
. ㈢緣由:Java 語言規范中指出:為了獲得最佳速度,允許線程保存共享成員變量的私有拷貝,而且只當線程進入或者離開同步代碼塊時才與共享成員變量的原始值對比。
這樣當多個線程同時與某個對象交互時,就必須要注意到要讓線程及時的得到共享成員變量的變化。
而 volatile 關鍵字就是提示 VM :對于這個成員變量不能保存它的私有拷貝,而應直接與共享成員變量交互。
. ㈣使用技巧:在兩個或者更多的線程訪問的成員變量上使用 volatile 。當要訪問的變量已在 synchronized 代碼塊中,或者為常量時,不必使用。
由于使用 volatile 屏蔽掉了 VM 中必要的代碼優化,所以在效率上比較低,因此一定在必要時才使用此關鍵字。
闡釋:
線程為了提高效率,將某成員變量(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的是B。
只在某些動作時才進行A和B的同步,因此存在A和B不一致的情況。volatile就是用來避免這種情況的。
volatile告訴jvm,它所修飾的變量不保留拷貝,直接訪問主內存中的(也就是上面說的A)
sleep() vs wait()
sleep是線程類(Thread)的方法,導致此線程暫停執行指定時間,把執行機會給其他線程,但是監控狀態依然保持,到時后會自動恢復。
調用sleep不會釋放對象鎖。
wait是Object類的方法,對此對象調用wait方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發出notify方法(或notifyAll)后本線程才
進入對象鎖定池準備獲得對象鎖進入運行狀態。
===================2007-05-01引用如下內容===========
早在Java 1.2推出之時,Java平臺中就引入了一個新的支持:java.lang.ThreadLocal,給我們在編寫多線程程序時提供了一種新的選擇。使用這個工具類可以很簡潔地編寫出優美的多線程程序,雖然ThreadLocal非常有用,但是似乎現在了解它、使用它的朋友還不多。
ThreadLocal是什么 ThreadLocal是什么呢?其實ThreadLocal并非是一個線程的本地實現版本,它并不是一個Thread,而是thread local variable(線程局部變量)。也許把它命名為ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。從線程的角度看,就好像每一個線程都完全擁有該變量。線程局部變量并不是Java的新發明,在其它的一些語言編譯器實現(如IBM XL FORTRAN)中,它在語言的層次提供了直接的支持。因為Java中沒有提供在語言層次的直接支持,而是提供了一個ThreadLocal的類來提供支持,所以,在Java中編寫線程局部變量的代碼相對比較笨拙,這也許是線程局部變量沒有在Java中得到很好的普及的一個原因吧。
ThreadLocal的設計 首先看看ThreadLocal的接口:
Object get() ; // 返回當前線程的線程局部變量副本 protected Object initialValue(); // 返回該線程局部變量的當前線程的初始值void set(Object value); // 設置當前線程的線程局部變量副本的值
ThreadLocal有3個方法,其中值得注意的是initialValue(),該方法是一個protected的方法,顯然是為了子類重寫而特意實現的。該方法返回當前線程在該線程局部變量的初始值,這個方法是一個延遲調用方法,在一個線程第1次調用get()或者set(Object)時才執行,并且僅執行1次。ThreadLocal中的確實實現直接返回一個null:
protected Object initialValue() { return null; } |
ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用于存儲每一個線程的變量的副本。比如下面的示例實現:
public class ThreadLocal { private Map values = Collections.synchronizedMap(new HashMap()); public Object get() { Thread curThread = Thread.currentThread(); Object o = values.get(curThread); if (o == null && !values.containsKey(curThread)) { o = initialValue(); values.put(curThread, o); } return o; }
public void set(Object newValue) { values.put(Thread.currentThread(), newValue); }
public Object initialValue() { return null; } } |
當然,這并不是一個工業強度的實現,但JDK中的ThreadLocal的實現總體思路也類似于此。
ThreadLocal的使用
如果希望線程局部變量初始化其它值,那么需要自己實現ThreadLocal的子類并重寫該方法,通常使用一個內部匿名類對ThreadLocal進行子類化,比如下面的例子,SerialNum類為每一個類分配一個序號:
public class SerialNum { // The next serial number to be assigned
private static int nextSerialNum = 0; private static ThreadLocal serialNum = new ThreadLocal() { protected synchronized Object initialValue() { return new Integer(nextSerialNum++); } };
public static int get() { return ((Integer) (serialNum.get())).intValue(); } } |
SerialNum類的使用將非常地簡單,因為get()方法是static的,所以在需要獲取當前線程的序號時,簡單地調用:
int serial = SerialNum.get(); |
即可。
在線程是活動的并且ThreadLocal對象是可訪問的時,該線程就持有一個到該線程局部變量副本的隱含引用,當該線程運行結束后,該線程擁有的所以線程局部變量的副本都將失效,并等待垃圾收集器收集。
ThreadLocal與其它同步機制的比較
ThreadLocal和其它同步機制相比有什么優勢呢?ThreadLocal和其它所有的同步機制都是為了解決多線程中的對同一變量的訪問沖突,在普通的同步機制中,是通過對象加鎖來實現多個線程對同一變量的安全訪問的。這時該變量是多個線程共享的,使用這種同步機制需要很細致地分析在什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放該對象的鎖等等很多。所有這些都是因為多個線程共享了資源造成的。ThreadLocal就從另一個角度來解決多線程的并發訪問,ThreadLocal會為每一個線程維護一個和該線程綁定的變量的副本,從而隔離了多個線程的數據,每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的整個變量封裝進ThreadLocal,或者把該對象的特定于線程的狀態封裝進ThreadLocal。
由于ThreadLocal中可以持有任何類型的對象,所以使用ThreadLocal get當前線程的值是需要進行強制類型轉換。但隨著新的Java版本(1.5)將模版的引入,新的支持模版參數的ThreadLocal<T>類將從中受益。也可以減少強制類型轉換,并將一些錯誤檢查提前到了編譯期,將一定程度地簡化ThreadLocal的使用。
總結
當然ThreadLocal并不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個線程對相同資源的并發訪問,是為了多個線程之間進行通信的有效方式;而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量),這樣當然不需要對多個線程進行同步了。所以,如果你需要進行多個線程之間進行通信,則使用同步機制;如果需要隔離多個線程之間的共享沖突,可以使用ThreadLocal,這將極大地簡化你的程序,使程序更加易讀、簡潔。