Preface
最近看了一下<Java Concurrency In Practice> 這本書, 總體來說還是一本不錯(cuò)的書, 不過粒度不夠細(xì), 是從大的角度, 例如: 設(shè)計(jì)整體項(xiàng)目上如何考慮并發(fā)的多方面因素,不過總體上來說還是一本不錯(cuò)的書,結(jié)合部分網(wǎng)絡(luò)上的資料,總結(jié)一下自己的知識(shí),免的忘了。
下面是一些最基本的知識(shí),不想再寫了,反正網(wǎng)上多的是,挑了一篇還不錯(cuò)的轉(zhuǎn)過來,大家要支持別人的成果O:
http://yanxuxin.iteye.com/blog/547261 對(duì)于進(jìn)程的概念我們都很熟悉,它是應(yīng)用程序級(jí)的隔離,不同的應(yīng)用程序之間的進(jìn)程幾乎不共享任何資源。而線程則可以說是應(yīng)用程序內(nèi)的隔離,一種相對(duì)低級(jí)別的隔離。一個(gè)進(jìn)程可以有多個(gè)線程,它們之間隔離的內(nèi)容大致包括:a.自身的堆棧,b.程序計(jì)數(shù)器,c.局部變量;共享應(yīng)用的內(nèi)容大致包括:a.內(nèi)存,b.文件句柄,c.進(jìn)程狀態(tài)等。線程不是Java自身的概念,它是操作系統(tǒng)底層的概念。Java作為一種應(yīng)用語言把線程的操作通過API提升到應(yīng)用開發(fā)的支持,但是在并發(fā)性的支持上并不是那么美好。 Java在設(shè)計(jì)時(shí),每個(gè)對(duì)象都有一個(gè)隱式的鎖,這個(gè)鎖的使用則是通過synchronized關(guān)鍵字來顯式的使用。在JDK5.0以后引用了java.util.concurrent.ReentrantLock作為synchronized之外的選擇,配和Condition可以以一種條件鎖的機(jī)制來管理并發(fā)的線程,之后的總結(jié)再介紹。提到synchronized,多數(shù)的初學(xué)者都知道Object的wait(),notify(),notifyAll()是配和其使用的,但是為什么要在同步內(nèi)才能用對(duì)象的這些方法呢(不然拋IllegalMonitorStateException)? 我想因?yàn)闆]有synchronized讓對(duì)象的隱式鎖發(fā)揮作用,那么方法或者方法塊內(nèi)的線程在同一時(shí)間可能存在多個(gè),假設(shè)wait()可用,它會(huì)把這些線程統(tǒng)統(tǒng)的加到wait set中等待被喚醒,這樣永遠(yuǎn)沒有多余的線程去喚醒它們。每個(gè)對(duì)象管理調(diào)用其wait(),notify()的線程,使得別的對(duì)象即使想幫忙也幫不上忙。這樣的結(jié)果就是多線程永遠(yuǎn)完成不了多任務(wù),基于此Java在設(shè)計(jì)時(shí)使其必須與synchronized一起使用,這樣獲得隱式鎖的線程同一時(shí)間只有一個(gè),當(dāng)此線程被對(duì)象的wait()扔到wait set中時(shí),線程會(huì)釋放這個(gè)對(duì)象的隱式鎖等待被喚醒的機(jī)會(huì),這樣的設(shè)計(jì)會(huì)大大降低死鎖。另外同一個(gè)對(duì)象隱式鎖作用下的多個(gè)方法或者方法塊在沒有鎖的限制下可以同時(shí)允許多個(gè)線程在不同的方法內(nèi)wait和notify,嚴(yán)重的競(jìng)爭(zhēng)條件使得死鎖輕而易舉。所以Java設(shè)計(jì)者試圖通過Monitor Object模式解決這些問題,每個(gè)對(duì)象都是Monitor用于監(jiān)視擁有其使用權(quán)的線程。 但是synchronized這種獲得隱式鎖的方式本身也是有隱患問題的:
a.不能中斷正在試圖獲得鎖的線程,
b.試圖獲得鎖時(shí)不能設(shè)定超時(shí),
c.每個(gè)鎖只有一個(gè)條件太少。
對(duì)于最后一項(xiàng)的設(shè)計(jì)前面提到的JDK5的方案是可以彌補(bǔ)的,一個(gè)ReentrantLock可以有多個(gè)Condition,每個(gè)條件管理獲得對(duì)象鎖滿足條件的線程,通過await(),signalAll()使只關(guān)于Condition自己放倒的線程繼續(xù)運(yùn)行,或者放倒一些線程,而不是全部喚醒等等。但對(duì)于前兩者的極端情況會(huì)出現(xiàn)死鎖。下面的這個(gè)例子: - class DeadLockSample{
- public final Object lock1 = new Object();
- public final Object lock2 = new Object();
-
- public void methodOne(){
- synchronized(lock1){
- ...
- synchronized(lock2){...}
- }
- }
-
- public void methodTwo(){
- synchronized(lock2){
- ...
- synchronized(lock1){...}
- }
- }
- }
假設(shè)場(chǎng)景:線程A調(diào)用methodOne(),獲得lock1的隱式鎖后,在獲得lock2的隱式鎖之前線程B進(jìn)入運(yùn)行,調(diào)用methodTwo(),搶先獲得了lock2的隱式鎖,此時(shí)線程A等著線程B交出lock2,線程B等著lock1進(jìn)入方法塊,死鎖就這樣被創(chuàng)造出來了。 以上的例子不直觀的話,再看一個(gè)實(shí)例順便看看wait()的缺陷: - import java.util.LinkedList;
- import java.util.List;
-
- /**
- * User: yanxuxin
- * Date: Dec 9, 2009
- * Time: 5:58:39 PM
- */
- public class DeadLockSample {
- public static void main(String[] args) {
- final WaitAndNotify wan = new WaitAndNotify();
-
- Thread t1 = new Thread(new Runnable(){
- public void run() {
- wan.pop();
- }
- });
-
- Thread t2 = new Thread(new Runnable(){
- public void run() {
- wan.push("a");
- }
- });
-
- t1.start();
- t2.start();
- }
- }
-
- class WaitAndNotify {
-
- final List<String> list = new LinkedList<String>();
-
- public synchronized void push(String x) {
- synchronized(list) {
- list.add(x);
- notify();
- }
- }
-
- public synchronized Object pop() {
- synchronized(list) {
- if(list.size() <= 0) {
- try {
- wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- return list.size();
- }
- }
-
- }
上面的這個(gè)例子也會(huì)出現(xiàn)死鎖,為什么呢?首先看WaitAndNotify這個(gè)類,在push和pop方法上有synchronized關(guān)鍵字,方法內(nèi)部也有synchronized,那么當(dāng)WaitAndNotify實(shí)例化時(shí)會(huì)有兩個(gè)對(duì)象的隱式鎖,一個(gè)是WaitAndNotify對(duì)象自身的,作用在方法上;另一個(gè)就是方法內(nèi)部同步用到的list的。主線程開啟兩個(gè)線程t1和t2,t1進(jìn)入pop方法此時(shí)list為空,它先后獲得了wan和list的隱式鎖,接著就被wait扔進(jìn)wait set等待去了。注意這個(gè)wait()方法是誰的?答案是wan的,所以它釋放了wan的隱式鎖,但是把list的死死的抓著不放。此時(shí)t2終于得到了wan的隱式鎖進(jìn)入push方法,但是不幸的是list的隱式鎖它這輩子也得不到了。。。
此外synchronized的重點(diǎn)說的簡(jiǎn)單:它就是配和對(duì)象的隱式鎖使用的,注意一定是對(duì)象的隱式鎖!那么下面的這個(gè)例子又怎么解釋呢? -
-
-
-
-
- public class ImplicitLockSample {
-
- public static void main(String[] args) {
- final ImplicitLock sample = new ImplicitLock();
-
- new Thread(new Runnable() {
- public void run() {
-
- sample.method1();
- }
- }).start();
-
- new Thread(new Runnable() {
- public void run() {
- sample.method2();
- }
- }).start();
- }
- }
-
-
- class ImplicitLock {
-
- public static synchronized void method1() {
- System.out.println("method1 executing...");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- public synchronized void method2() {
- System.out.println("method2 executing...");
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
這里ImplicitLock有兩個(gè)同步方法,一個(gè)是static的,一個(gè)是普通的。ImplicitLockSample是一個(gè)測(cè)試主程序,實(shí)例化一個(gè)ImplicitLock對(duì)象,并且開啟兩個(gè)線程,每個(gè)線程分別調(diào)用對(duì)象的method1和method2方法。每個(gè)進(jìn)入方法的線程都會(huì)強(qiáng)制休眠3秒。那么執(zhí)行的現(xiàn)象是什么呢? 要知道答案有以下幾點(diǎn)要清楚:a.Class和Object的關(guān)系,b.static方法的含義,c.synchronized的機(jī)制,d.sleep的作用。清楚的知道這些之后,一眼就能辨別method1和method2方法上的synchronized配和的是兩把不同的對(duì)象隱式鎖。答案也就清晰的知道這兩個(gè)線程執(zhí)行的打印語句根本就不會(huì)相差近3秒的等待,而是幾乎同時(shí)。下面我試著解釋一下。 Class是Object的子類,說明了Class是特殊的對(duì)象,它自然也有對(duì)象隱式鎖。static聲明方法意味著這個(gè)方法不依賴于類的實(shí)例,而是可以理解成去掉了隱式參數(shù)this的,類對(duì)象的方法。synchronized是與對(duì)象隱式鎖綁定的,這代表了將其置于方法聲明上它將與方法的持有對(duì)象綁定。所以method1的同步鎖是ImplicitLock類對(duì)象的隱式鎖,而method2的同步鎖是ImplicitLock實(shí)例對(duì)象的隱式鎖。sleep雖然能讓當(dāng)前的線程休眠,但是它不會(huì)釋放持有的隱式鎖。這樣主程序執(zhí)行是雖然用同一個(gè)實(shí)例讓兩個(gè)線程分別去調(diào)用兩個(gè)方法,但是它們之間并沒有任何競(jìng)爭(zhēng)鎖的關(guān)系,所以幾乎同時(shí)打印,不會(huì)有近3秒的間隔。把method1的調(diào)用改成已注釋的代碼將更容易理解。如果method1的synchronized去掉,或者method2加上synchronized的聲明,那么它們將競(jìng)爭(zhēng)同一個(gè)隱式鎖。先獲得鎖的線程將在3秒后交出鎖,后面的線程才能執(zhí)行打印。 寫這篇補(bǔ)遺源自于對(duì)懶漢式單例的重新理解,之前對(duì)synchronized的機(jī)制不明了時(shí),只知道使用synchronized關(guān)鍵字在static方法上聲明就能保證單例的線程安全,但是確不知道那算是誤打誤撞的理解。構(gòu)造這個(gè)驗(yàn)證例子之前,static和synchronized的共同使用讓我對(duì)synchronized隱式鎖有了更清晰的認(rèn)識(shí)。所以打算再寫寫來分享這段體會(huì)。
volatile關(guān)鍵字
Volatile是JDK7里的核心,為什么,隨便看一下LinkedBlockQueue, head 等關(guān)鍵就會(huì)發(fā)現(xiàn)不懂volatile那什么都是扯。。
volatile就是被認(rèn)為“輕量級(jí)的synchronized”,但是使用其雖然可以簡(jiǎn)化同步的編碼,并且運(yùn)行開銷相對(duì)于JVM沒有優(yōu)化的競(jìng)爭(zhēng)線程同步低,但是濫用將不能保證程序的正確性。鎖的兩個(gè)特性是:互斥和可見。互斥保證了同時(shí)只有一個(gè)線程持有對(duì)象鎖進(jìn)行共享數(shù)據(jù)的操作,從而保證了數(shù)據(jù)操作的原子性,而可見則保證共享數(shù)據(jù)的修改在下一個(gè)線程獲得鎖后看到更新后的數(shù)據(jù)。volatile僅僅保證了無鎖的可見性,但是不提供原子性操作的保證!這是因?yàn)関olatile關(guān)鍵字作用的設(shè)計(jì)是JVM阻止volatile變量的值放入處理器的寄存器,在寫入值以后會(huì)被從處理器的cache中flush掉,寫到內(nèi)存中去。這樣讀的時(shí)候限制處理器的cache是無效的,只能從內(nèi)存讀取值,保證了可見性。從這個(gè)實(shí)現(xiàn)可以看出volatile的使用場(chǎng)景:多線程大量的讀取,極少量或者一次性的寫入,并且還有其他限制。 由于其無法保證“讀-修改-寫”這樣操作的原子性(當(dāng)然java.util.concurrent.atomic包內(nèi)的實(shí)現(xiàn)滿足這些操作,主要是通過CAS--比較交換的機(jī)制,后續(xù)會(huì)嘗試寫寫。),所以像++,--,+=,-=這樣的變量操作,即使聲明volatile也不會(huì)保證正確性。圍繞這個(gè)原理的主題,我們可以大致的整理一下volatile代替synchronized的條件:對(duì)變量的寫操作不依賴自身的狀態(tài)。所以除了剛剛介紹的操作外,例如: - private volatile boolean flag;
- if(!flag) {
- flag == true;
- }
類似這樣的操作也是違反volatile使用條件的,很可能造成程序的問題。所以使用volatile的簡(jiǎn)單場(chǎng)景是一次性的寫入之后,大量線程的讀取并且不再改變變量的值(如果這樣的話,都不是并發(fā)了)。這個(gè)關(guān)鍵字的優(yōu)勢(shì)還是在于多線程的讀取,既保證了讀取的低開銷(與單線程程序變量差不多),又能保證讀到的是最新的值。所以利用這個(gè)優(yōu)勢(shì)我們可以結(jié)合synchronized使用實(shí)現(xiàn)低開銷讀寫鎖: -
-
-
-
-
- public class AnotherSyncSample {
- private volatile int counter;
-
- public int getCounter() {
- return counter;
- }
-
- public synchronized void add() {
- counter++;
- }
- }
這個(gè)簡(jiǎn)單的例子在讀的方法上沒有使用synchronized關(guān)鍵字,所以讀的操作幾乎沒有等待;而由于寫的操作是原子性的違反了使用條件,不能得到保證,所以使用synchronized同步得到寫的正確性保證,這個(gè)模型在多讀取少寫入的實(shí)際場(chǎng)景中應(yīng)該要比都用synchronized的性能有不小的提升。 另外還有一個(gè)使用volatile的好處,得自于其原理:內(nèi)部禁止改變兩個(gè)volatile變量的賦值或者初始化順序,并且嚴(yán)格限制volatile變量和其周圍非volatile變量的賦值或者初始化順序。 -
-
-
-
-
- public class VolatileTest {
- public static void main(String[] args) {
- final VolatileSample sample = new VolatileSample();
-
- new Thread(new Runnable(){
- public void run() {
- sample.finish();
- }
- }).start();
-
- new Thread(new Runnable(){
- public void run() {
- sample.doSomething();
- }
- }).start();
- }
- }
-
- class VolatileSample {
- private volatile boolean finished;
- private int lucky;
-
- public void doSomething() {
- if(finished) {
- System.out.println("lucky: " + lucky);
- }
- }
-
- public void finish() {
- lucky = 7;
- finished = true;
- }
- }
這里首先線程A執(zhí)行finish(),完成finished變量的賦值后,線程B進(jìn)入方法doSomething()讀到了finish的值為true,打印lucky的值,預(yù)想狀態(tài)下為7,這樣完美的執(zhí)行結(jié)束了。但是,事實(shí)是如果finished變量不是聲明了volatile的話,過程就有可能是這樣的:線程A執(zhí)行finish()先對(duì)finished賦值,與此同時(shí)線程B進(jìn)入doSomething()得到finished的值為true,打印lucky的值為0,鏡頭切回線程A,接著給lucky賦值為7,可憐的是這個(gè)幸運(yùn)數(shù)字不幸杯具了。因?yàn)檫@里發(fā)生了扯淡的事情:JVM或許為了優(yōu)化執(zhí)行把兩者的賦值順序調(diào)換了。這個(gè)結(jié)果在單線程的程序中簡(jiǎn)直絕對(duì)一定肯定就是不可能,遺憾的是多線程存在這個(gè)隱患。
ThreadLocal
作為一個(gè)JDK5以后支持范型的類,主要是想利用范型把非線程安全的共享變量,封裝成綁定線程的安全不共享變量。這樣的解釋我想我們多半能猜出它的實(shí)現(xiàn)思路:把一個(gè)共享變量在每個(gè)線程使用時(shí),初始化一個(gè)副本,并且和線程綁定。以后所有的線程對(duì)共享變量的操作都是對(duì)線程內(nèi)部那個(gè)副本,完全的線程內(nèi)部變量的操作。 要實(shí)現(xiàn)這樣功能類的設(shè)計(jì),主要技術(shù)點(diǎn)是要能把副本和線程綁定映射,程序可以安全查找到當(dāng)前線程的副本,修改后安全的綁定給線程。所以我們想到了Map的存儲(chǔ)結(jié)構(gòu),ThreadLocal內(nèi)部就是使用了線程安全的Map形式的存儲(chǔ)把currentThread和變量副本一一映射。 既然要把共享的變成不共享的,那么就要變量滿足一個(gè)場(chǎng)景:變量的狀態(tài)不需要共享。例如無狀態(tài)的bean在多線程之間是安全的,因?yàn)榫€程之間不需要同步bean的狀態(tài),用了就走(很不負(fù)責(zé)啊),想用就用。但是對(duì)于有狀態(tài)的bean在線程之間則必須小心,線程A剛看到狀態(tài)是a,正想利用a做事情,線程B把bean的狀態(tài)改為了b,結(jié)果做了不該做的。但是如果有狀態(tài)的bean不需要共享狀態(tài),每個(gè)線程看到狀態(tài)a或者b都可以做出自己的行為,這種情況下不同步的選擇就是ThreadLocal了。 利用ThreadLocal的優(yōu)勢(shì)就在于根本不用擔(dān)心有狀態(tài)的bean為了狀態(tài)的一致而犧牲性能,去使用synchronized限制只有一個(gè)線程在同一時(shí)間做出關(guān)于bean狀態(tài)的行為。而是多個(gè)線程同時(shí)根據(jù)自己持有的bean的副本的狀態(tài)做出行為,這樣的轉(zhuǎn)變對(duì)于并發(fā)的支持是那么的不可思議。例如一個(gè)Dao內(nèi)有個(gè)Connection的屬性,當(dāng)多個(gè)線程使用Dao的同一個(gè)實(shí)例時(shí),問題就來了:多個(gè)線程用一個(gè)Connection,而且它還是有連接,關(guān)閉等等的狀態(tài)轉(zhuǎn)變的,我們很敏感的想到這個(gè)屬性不安全!再看這個(gè)屬性,其實(shí)它是多么的想告訴線程哥哥們:我的這些狀態(tài)根本就不想共享,不要因?yàn)槲业臓顟B(tài)而不敢一起追求。線程哥哥們也郁悶:你要是有多胞胎姐妹該多好啊!這時(shí)候ThreadLocal大哥過來說:小菜,我來搞定!你們這些線程一人一個(gè)Connection,你想關(guān)就關(guān),想連接就連接,再也不用抱怨說它把你的連接關(guān)了。這樣Dao的實(shí)例再也不用因?yàn)樽约河袀€(gè)不安全的屬性而自卑了。當(dāng)然ThreadLocal的思路雖然是很好的,但是官方的說法是最初的實(shí)現(xiàn)性能并不好,隨著Map結(jié)構(gòu)和Thread.currentThread的改進(jìn),性能較之synchronized才有了明顯的優(yōu)勢(shì)。所以要是使用的是JDK1.2,JDK1.3等等,也不要妄想麻雀變鳳凰... 再看ThreadLocal和synchronized的本質(zhì)。前者不在乎多占點(diǎn)空間,但是絕對(duì)的忍受不了等待;后者對(duì)等待無所謂,但是就是不喜歡浪費(fèi)空間。這也反映出了算法的一個(gè)規(guī)律:通常是使用場(chǎng)景決定時(shí)間和空間的比例,既省時(shí)又省地的算法多數(shù)情況下只存在于幻想之中。下面寫個(gè)簡(jiǎn)單的例子解釋一下,不過個(gè)人覺得設(shè)計(jì)的例子不太好,以后有實(shí)際的啟發(fā)再替換吧。 - import java.util.concurrent.atomic.AtomicInteger;
-
- /**
- * User: yanxuxin
- * Date: Dec 14, 2009
- * Time: 9:26:41 PM
- */
- public class ThreadLocalSample extends Thread {
- private OperationSample2 operationSample;
-
- public ThreadLocalSample(OperationSample2 operationSample) {
- this.operationSample = operationSample;
- }
-
- @Override
- public void run() {
- operationSample.printAndIncrementNum();
- }
-
- public static void main(String[] args) {
-
- final OperationSample2 operation = new OperationSample2();//The shared Object for threads.
-
- for (int i = 0; i < 5; i++) {
- new ThreadLocalSample(operation).start();
- }
- }
- }
-
- class OperationSample {
- private int num;
-
- //public synchronized void printAndIncrementNum() {
- public void printAndIncrementNum() {
- for (int i = 0; i < 2; i++) {
- System.out.println(Thread.currentThread().getName() + "[id=" + num + "]");
- num += 10;
- }
- }
- }
-
- class OperationSample2 {
-
- private static ThreadLocal<Integer> threadArg = new ThreadLocal<Integer>() {
- @Override
- protected Integer initialValue() {
- return 0;
- }
- };
-
- public void printAndIncrementNum() {
- for (int i = 0; i < 2; i++) {
- int num = threadArg.get();
- threadArg.set(num + 10);
- System.out.println(Thread.currentThread().getName() + "[id=" + num + "]");
- }
- }
- }
-
- class OperationSample3 {
-
- private static final AtomicInteger uniqueId = new AtomicInteger(0);
- private static ThreadLocal<Integer> threadArg = new ThreadLocal<Integer>() {
- @Override
- protected Integer initialValue() {
- return uniqueId.getAndIncrement();
- }
- };
-
- public void printAndIncrementNum() {
- for (int i = 0; i < 2; i++) {
- int num = threadArg.get();
- threadArg.set(num + 10);
- System.out.println(Thread.currentThread().getName() + "[id=" + num + "]");
- }
- }
- }
這個(gè)例子中ThreadLocalSample繼承自Thread持有OperationSample三個(gè)版本中的一個(gè)引用,并且在線程運(yùn)行時(shí)執(zhí)行printAndIncrementNum()方法。 首先看版本1:OperationSample有個(gè)共享變量num,printAndIncrementNum()方法沒有同步保護(hù),方法就是循環(huán)給num賦新值并打印改變值的線程名。因?yàn)闆]有任何的同步保護(hù),所以原本打算每個(gè)線程打印出的值是相鄰遞加10的結(jié)果變成了不確定的遞加。有可能線程1的循環(huán)第一次打印0,第二次就打印50。這時(shí)候我們使用被注釋的方法聲明,結(jié)果就是預(yù)想的同一個(gè)線程的兩次結(jié)果是相鄰的遞加,因?yàn)橥粫r(shí)刻只有一個(gè)線程獲得OperationSample實(shí)例的隱式鎖完成循環(huán)釋放鎖。 再看版本2:假設(shè)我們有個(gè)遞增10的簡(jiǎn)單計(jì)數(shù)器,但是是對(duì)每個(gè)線程的計(jì)數(shù)。也就是說我們有一個(gè)Integer計(jì)數(shù)器負(fù)責(zé)每個(gè)線程的計(jì)數(shù)。雖然它是有狀態(tài)的,會(huì)變的,但是因?yàn)槊總€(gè)線程之間不需要共享變化,所以可以用ThreadLocal管理這個(gè)Integer。在這里看到我們的ThreadLocal變量的initialValue()方法被覆寫了,這個(gè)方法的作用就是當(dāng)調(diào)用ThreadLocal的get()獲取線程綁定的副本時(shí)如果還沒綁定則調(diào)用這個(gè)方法在Map中添加當(dāng)前線程的綁定映射。這里我們返回0,表示每個(gè)線程的初始副本在ThreadLocal的Map的紀(jì)錄都是0。再看printAndIncrementNum()方法,沒有任何的同步保護(hù),所以多個(gè)線程可以同時(shí)進(jìn)入。但是,每個(gè)線程通過threadArg.get()拿到的僅僅是自己的Integer副本,threadArg.set(num + 10)的也是自己的副本值。所以結(jié)果就是雖然線程的兩次循環(huán)打印有快有慢,但是每個(gè)線程的兩次結(jié)果都是0和10。 最后是版本3:和版本2的不同在于新加了一個(gè)uniqueId的變量。這個(gè)變量是java.util.concurrent.atomic包下的原子變量類。這是基于硬件支持的CAS(比較交換)原語的實(shí)現(xiàn),所以保證了++,--,+=,-=等操作的原子性。所以在ThreadLocal變量的initialValue()方法中使用uniqueId.getAndIncrement()將為每個(gè)線程初始化唯一不會(huì)重復(fù)的遞加1的Integer副本值。而結(jié)果就會(huì)變成5個(gè)線程的首次打印是0~4的5個(gè)數(shù)字,第二次每個(gè)線程的打印是線程對(duì)應(yīng)的首次數(shù)字加10的值。 對(duì)于ThreadLocal的使用,Spring的源碼中有大量的應(yīng)用,主要是要支持Singleton的實(shí)例管理,那么自身的一些Singleton的實(shí)現(xiàn)內(nèi)非線程安全的變量,屬性要用ThreadLocal隔離共享。同時(shí)我們?cè)谑褂肧pring的IOC時(shí)也要注意有可能多線程調(diào)用的注冊(cè)到IOC容器的Singleton型實(shí)例是否真的線程安全。另外java.util.concurrent.atomic內(nèi)的原子變量類簡(jiǎn)單的提了一下,再看看怎么能瞎編出東西來吧。
posted on 2012-06-25 09:37
Daniel 閱讀(1405)
評(píng)論(0) 編輯 收藏 所屬分類:
CoreJava