潛魚在淵

          Concentrating on Architectures.

          posts - 77, comments - 309, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          Java線程安全精解

          Posted on 2005-11-17 01:59 非魚 閱讀(23423) 評(píng)論(37)  編輯  收藏 所屬分類: Java技術(shù)

          一直不敢寫點(diǎn)什么,是因?yàn)閼?zhàn)戰(zhàn)兢兢,生怕寫的不好甚至寫錯(cuò)了會(huì)誤人子弟。隨筆可以隨便寫一下,不用太過計(jì)較,可是技術(shù)從來都要不得半點(diǎn)馬虎,差之毫厘,謬以千里啊!但敝帚自珍又不是我的風(fēng)格,雖然文筆不好,也要勉為其難了。廢話少說,進(jìn)入正題。

           

                 從我開始接觸Java的多線程起就總是覺得書上講的不是那么清楚。不是說讀完了不會(huì)寫,而是對(duì)寫出來的多線程代碼懵懵懂懂,不知道每一句會(huì)有什么影響,心里感覺忐忑。后來仔細(xì)研讀Java語言規(guī)范后,才慢慢搞明白一些細(xì)節(jié)。我主要想說的,也就是這些經(jīng)驗(yàn)吧。

           

                 首先要搞清楚的是線程的共享資源,共享資源是多線程中每個(gè)線程都要訪問的類變量或?qū)嵗兞浚蚕碣Y源可以是單個(gè)類變量或?qū)嵗兞浚部梢允且唤M類變量或?qū)嵗兞俊6嗑€程程序可以有多個(gè)共享資源。下面描述他們之間的一對(duì)多關(guān)系(*表示多):

                

                               多線程程序(1----共享資源(*----類變量或?qū)嵗兞浚?/span>1…*

           

          只有類變量和實(shí)例變量可以成為共享資源,細(xì)分如下:

          1.       實(shí)現(xiàn)線程的類(繼承Thread類、實(shí)現(xiàn)Runnable接口的類)的類變量、實(shí)例變量。

          2.       實(shí)現(xiàn)線程的類的類變量、實(shí)例變量的類變量、實(shí)例變量,可以不規(guī)范的寫為:TreadClass.ClassOrInstanceVar[.ClassOrInstanceVar]*[]*的內(nèi)容表示無限可重復(fù)。

          3.       不是實(shí)現(xiàn)線程的類,但其對(duì)象可能是線程的類變量或?qū)嵗兞俊H?/span>ServletEJB。這些類的類變量和實(shí)例變量,不規(guī)范的寫為:ServletOrEJB.ClassOrInstanceVar[.ClassOrInstanceVar]*

          4.       特別注意:局部變量、做為參數(shù)傳遞的非類變量、非實(shí)例變量不是共享資源。

           

          那么什么是線程安全呢?關(guān)于這個(gè)問題我在網(wǎng)上百度了一下(沒辦法,有時(shí)候GOOGLE用不了),發(fā)現(xiàn)不少人在問這個(gè)問題,也有不少錯(cuò)誤的理解。所以我給出一個(gè)較容易理解的解釋:在線程中使用共享資源時(shí),能夠保證共享資源在任何時(shí)候都是原子的、一致的,這樣的線程就是線程安全的線程。還不太理解?沒有關(guān)系,慢慢解釋。

           

          首先來介紹一下共享資源的類型(這是我自己分類的,為了后文好解釋),共享資源從其類型可以分為三類(下文講到變量一律指類變量或?qū)嵗兞浚辉偬貏e指出):

          1.       獨(dú)立的基本類型共享資源,如一個(gè)簡(jiǎn)單的int變量,例:

          public class Cls1 {

                 private int a;

                 public int getA(){return a;}

                 public void setA(int a){this.a = a;}

          }

          可以看到a沒有任何依賴。

          public class Cls2{

                 private int a;

                 private int b;

                 private int c;

                 // 沒有對(duì)a的訪問方法,aCls外不可見。

          }

          假設(shè)上面類中bc都不依賴a,則a是這種類型。

           

          2.       相互依賴的基本類型共享資源,一個(gè)類中的幾個(gè)基本類型變量互相依賴,但從對(duì)象設(shè)計(jì)的角度又不能單獨(dú)把這幾個(gè)變量設(shè)計(jì)成一個(gè)類。

          假設(shè)上例Cls2中的bc互相依賴,則屬此種情況。

          3.       64位的基本類型變量。這個(gè)比較特殊,因?yàn)槟承C(jī)器上64變量會(huì)分成兩個(gè)32位的操作,所以和1不一樣。如doublelong類型。

          4.       類類型的共享資源。如下例中的obj

          public class Cls3{

                 private SomeObj obj;

          }

          public class SomeObj{

                 private int a;

                 private int b;

          }

           

                 其次來看看什么是原子性、一致性。其實(shí)在這里我借用了事務(wù)ACID屬性的AC,熟悉的朋友就不用我廢話了。所謂原子性,是指一個(gè)共享資源的所有屬性在任何時(shí)刻都是一起變化、密不可分的;所謂一致性,是指一個(gè)共享資源的所有屬性在變化之后一定會(huì)達(dá)到一個(gè)一致的狀態(tài)。

           

                 最后根據(jù)上述四種共享資源類型,來看看如何做到線程安全。

           

          1.       不用做什么,只一個(gè)獨(dú)立的變量,任何時(shí)候它都是原子、一致的。

          2.       使用synchronized關(guān)鍵字,保證幾個(gè)變量被一起修改、一起讀取。

          3.       使用volatile關(guān)鍵字,然后就和1一樣了。

          4.       2一樣處理。

           

          當(dāng)對(duì)訪問共享資源的方法不同時(shí)使用synchronized關(guān)鍵字時(shí),是什么樣一種情況呢?這是需要特別注意的,這樣不能保證線程安全!看看下面例子的運(yùn)行結(jié)果就知道了(自己運(yùn)行啊,我不貼結(jié)果了):

          /**

           * $Author: $

           * $Date: $

           * $Revision: $

           * $History: $

           *

           * Created by feelyou, at time 22:31:53, 2005-11-16.

           */

           

          public class TestThread extends Thread {

           

            private int a = 0;

            private int b = 0;

           

            public static void main(String[] args) {

              TestThread test = new TestThread();

              for (int i = 0; i < 10; i++) {

                Thread thread = new Thread(test, "thread-" + i);

                thread.start();

              }

            }

           

            public synchronized void doWrite() {

              a++;

              try {

                sleep((int)(Math.random()*100));

              }

              catch (InterruptedException e) {

              }

              b++;

              try {

                sleep((int)(Math.random()*100));

              }

              catch (InterruptedException e) {

              }

            }

           

            public void print() {

              System.out.println("" + Thread.currentThread().getName() + ":a:" + a);

              System.out.println("" + Thread.currentThread().getName() + ":b:" + b);

            }

           

            public void run() {

              super.run();    //To change body of overridden methods use File | Settings | File Templates.

              for (int i = 0; i < 10; i++) {

                doWrite();

                print();

              }

            }

           

            public synchronized void start() {

              super.start();    //To change body of overridden methods use File | Settings | File Templates.

            }

          }

           

          ThreadLocalThreadLocal對(duì)于線程安全還是很有用的,如果資源不是共享的,那么應(yīng)該使用ThreadLocal,但如果確實(shí)需要在線程間共享資源,ThreadLocal就沒有用了!

           

          最后,來一個(gè)完整的線程安全的例子:

          /**

           * $Author: $

           * $Date: $

           * $Revision: $

           * $History: $

           *

           * Created by feelyou, at time 22:31:53, 2005-11-16.

           */

           

          public class TestThread extends Thread {

           

            private int a = 0; //獨(dú)立的共享資源

            private int b = 0; //bc互相依賴

            private int c = 0;

            private volatile long d = 0L; //64

          //  private SomeObj obj = new SomeObj(); //對(duì)象類型,大家自己寫吧,我就不寫了。

           

            public static void main(String[] args) {

              TestThread test = new TestThread();

              for (int i = 0; i < 10; i++) {

                Thread thread = new Thread(test, "thread-" + i);

                thread.start();

              }

            }

           

            public synchronized void doWrite() {

              b++;

              try {

                sleep((int)(Math.random()*100));

              }

              catch (InterruptedException e) {

              }

              c++;

              try {

                sleep((int)(Math.random()*100));

              }

              catch (InterruptedException e) {

              }

            }

           

            public synchronized void print() {

              System.out.println("" + Thread.currentThread().getName() + ":b:" + b);

              System.out.println("" + Thread.currentThread().getName() + ":c:" + c);

            }

           

            private void setA(int a) {

                this.a = a;

            }

           

            private int getA() {

                return a;

            }

           

            public long getD() {

                return d;

            }

           

            public void setD(long d) {

                this.d = d;

            }

           

            public void run() {

              super.run();    //To change body of overridden methods use File | Settings | File Templates.

              for (int i = 0; i < 10; i++) {

                doWrite();

                print();

                setA(i);

                System.out.println(getA());

                setD(18456187413L * i);

                System.out.println(getD());

              }

            }

           

            public synchronized void start() {

              super.start();    //To change body of overridden methods use File | Settings | File Templates.

            }

          }

          寫的比較匆忙,如果有錯(cuò)誤,還請(qǐng)大家指正,謝謝!

          評(píng)論

          # re: Java線程安全詳解  回復(fù)  更多評(píng)論   

          2005-12-16 10:44 by www
          什么詳解啊 胡說八到,

          # re: Java線程安全詳解  回復(fù)  更多評(píng)論   

          2005-12-16 12:20 by 非魚
          寫的比較匆忙,如果有錯(cuò)誤,還請(qǐng)大家指正。

          可能還不夠詳細(xì)吧,但是哪里胡說八道了?拜托,如果有錯(cuò)誤請(qǐng)明確指出好不好?

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2006-01-19 00:36 by aaa
          還可以呀。呵呵。有用

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2006-01-24 15:53 by 路過..
          還不錯(cuò).是我見過少數(shù)描述線程安全沒有什么錯(cuò)誤理解的文章..
          使用同步比較容易造成性能影響甚至死瑣情況,
          簡(jiǎn)單的辦法就是共享的類不使用實(shí)例變量,都改用局部變量.
          當(dāng)一個(gè)單例的類有實(shí)例變量時(shí)就要小心他很可能不是線程安全的了.

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2006-02-27 20:54 by mrwjx
          沒看懂,關(guān)于 “共享資源的類型” 這一段,能用更通俗的話講一下嗎,我看了好幾遍,還是不明白您的意思,另外關(guān)于線程安全簡(jiǎn)單的講,是不是可以理解成多個(gè)線程調(diào)用線程安全的類的方法的話,不會(huì)出現(xiàn)不可預(yù)知的不正常的情況

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2006-04-10 19:20 by java愛好者
          既然你害怕寫東西,就不要寫了吧,你的思維思路是沒錯(cuò)的,但是你的表達(dá)很不清晰,很容易給初學(xué)者造成迷茫困惑,呵呵,以后多寫點(diǎn)代碼吧

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2006-07-11 15:15 by mn
          樓上的真是sb

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2006-08-13 09:03 by loocky
          寫的其實(shí)不錯(cuò)

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2006-08-27 23:12 by happy
          人家好心好意寫出來與大家分享,大家應(yīng)該支持一下啊!
          有問題認(rèn)真討論,出口傷人就太不應(yīng)該了。

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2006-12-10 10:35 by lyl
          不錯(cuò),謝謝,辛苦了

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2007-03-31 23:32 by xzw
          寫的真的很不錯(cuò)的,謝謝了

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2007-06-24 22:46 by 肖建
          你雞兒屎的,寫的還不錯(cuò),
          你對(duì)JAVA的線程安全已經(jīng)理解的很不錯(cuò)了,
          我很佩服你哈,
          你是不是搞了很久這方面的東西了啊,
          不然是沒有這么深厚的功底的哈

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2007-06-28 16:25 by sinkos
          我很欣賞這位作者:一直不敢寫點(diǎn)什么,是因?yàn)閼?zhàn)戰(zhàn)兢兢,生怕寫的不好甚至寫錯(cuò)了會(huì)誤人子弟。隨筆可以隨便寫一下,不用太過計(jì)較,可是技術(shù)從來都要不得半點(diǎn)馬虎,差之毫厘,謬以千里啊!
          這些話體現(xiàn)作者一絲不茍的態(tài)度,科學(xué)的態(tài)度.
          謝謝你,謝謝這位作者!

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2007-07-07 09:53 by itkui
          謝謝樓主分享。

          我感覺罵人是不對(duì)的,尤其沒有仔細(xì)看完就匆匆罵人的同志。

          哪里好哪里不好,你說出來嗎。

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2007-09-17 16:14 by 同聲傳譯
          愿意將自己的勞動(dòng)成果與別人分享其實(shí)就已經(jīng)很不錯(cuò)了。

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2007-09-28 11:04 by 大王
          不錯(cuò)。支持。

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2008-05-18 13:54 by 極地冰冷
          寫的不錯(cuò),

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2008-08-26 09:09 by yp
          不錯(cuò)

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2008-08-26 18:25 by 陳堯
          -,還不錯(cuò)啦.雖然沒看得很懂但是我覺得還是滿仔細(xì)的.
          那些講粗話的同志是不是要思考一下自己的素質(zhì)呢?..

          # re: Java線程安全精解[未登錄]  回復(fù)  更多評(píng)論   

          2008-09-19 08:50 by caoer
          純支持,繼續(xù)努力,共勉……

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2008-10-24 18:10 by xxyw
          看的不是很明白..不過謝謝樓主了..

          # re: Java線程安全精解[未登錄]  回復(fù)  更多評(píng)論   

          2008-11-17 17:23 by XXX
          還不錯(cuò)

          # re: Java線程安全精解[未登錄]  回復(fù)  更多評(píng)論   

          2008-11-25 01:12 by fbysss
          ---實(shí)現(xiàn)線程的類(繼承Thread類、實(shí)現(xiàn)Throwable接口的類)
          是不是寫錯(cuò)了?應(yīng)該是Runnable接口吧?

          另外<!--[if !supportLists]--> 這些是啥?模板標(biāo)簽沒替換呢?重新排一下版吧,看上去比較費(fèi)勁

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2009-01-13 18:30 by 讀鐵人
          你的第一個(gè)例子的那個(gè)結(jié)果確實(shí)是不同步的,不過你知道為什么嗎?寫數(shù)據(jù)和輸出數(shù)據(jù)的方法不是同步的,盡管你加了synchronized,但是run方法里面沒有加吧? public synchronized void f()
          {
          for (int i = 0; i < 10; i++) {

          doWrite();

          print();

          }
          }
          然后在run方法里面調(diào)用f()就行了。這就是原子性。
          共享資源簡(jiǎn)單點(diǎn)講就是線程要共用的變量,一般就是類變量,那像你說的怎么復(fù)雜。線程安全就是要保證原子性,你什么時(shí)候看你start方法前面要加synchronized的?

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2009-01-13 18:34 by 讀鐵人
          如果你覺得我說的不對(duì),可以來csdn找我,cy729215495,我也是絕不輕易發(fā)貼,誤人子弟。我在blogjava沒有注冊(cè),729215495也是我的qq號(hào)碼,可以交流。

          # re: Java線程安全精解[未登錄]  回復(fù)  更多評(píng)論   

          2009-02-11 16:02 by jade
          不錯(cuò)

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2009-03-16 20:02 by ee
          e

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2009-04-17 16:42 by weii
          還是要支持一下樓主啊!幸苦了,罵人的能寫出來嗎?

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2009-07-12 11:15 by 絕對(duì)路過
          Google"線程安全 JAVA"一下,這是第一篇。

          # re: Java線程安全精解[未登錄]  回復(fù)  更多評(píng)論   

          2009-09-07 17:30 by qq
          我想說的是這個(gè)繼承了Thread類的線程不會(huì)出現(xiàn)問題嗎

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2009-09-08 10:38 by 亂碼(luan.ma(a)163.com)
          來批批“4. 特別注意:局部變量、做為參數(shù)傳遞的非類變量、非實(shí)例變量不是共享資源。”

          void f(Object key)
          {
          Person person;
          if (map.contains(key)) {
          person = map.get(key);
          }
          else {
          person = new Person();
          map.put(key, person);
          }

          synchronized (person) {
          ...
          //對(duì) person 的一些操作
          ...
          }
          }

          LZ請(qǐng)看以上代碼

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2009-11-06 10:46 by remox
          共享資源什么的 要考慮java的內(nèi)存模型吧

          理論上所有資源都可以成為共享資源,實(shí)際情況也的確如此

          但有些資源是無法不成為共享資源,是必然的共享資源,比如靜態(tài)變量

          這里就涉及到線程工作棧和主存的同步,

          那對(duì)于每個(gè)線程自身的存儲(chǔ)區(qū)域又是如何分配的呢

          究竟Java的內(nèi)存模型在多線程環(huán)境中是如何進(jìn)行對(duì)資源的管理的。

          你說的這些距離精解還是不夠的啊

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2009-11-25 10:09 by ddy
          看的出來lz很用心,先謝謝lz了。
          也許是因?yàn)閘z一直都“戰(zhàn)戰(zhàn)兢兢”,少有這么公開表達(dá)觀點(diǎn),乃至表達(dá)不清。就這篇文章,我作為初學(xué)者看起來很吃力,在表達(dá)方式、條理性上lz還可以有改進(jìn)。
          再次謝謝lz

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2009-12-11 11:32 by 路過
          你的第一個(gè)例子的那個(gè)結(jié)果確實(shí)是不同步的,不過你知道為什么嗎?寫數(shù)據(jù)和輸出數(shù)據(jù)的方法不是同步的,盡管你加了synchronized,但是run方法里面沒有加吧? public synchronized void f()
          {
          for (int i = 0; i < 10; i++) {

          doWrite();

          print();

          }
          }
          然后在run方法里面調(diào)用f()就行了。這就是原子性。
          共享資源簡(jiǎn)單點(diǎn)講就是線程要共用的變量,一般就是類變量,那像你說的怎么復(fù)雜。線程安全就是要保證原子性,你什么時(shí)候看你start方法前面要加synchronized的?
          說得太好了

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2010-08-27 12:27 by 7510
          你這個(gè)也敢叫精解?!
          不要誤人子弟了
          趕緊把文章刪了
          還列g(shù)oogle第一條呢

          # re: Java線程安全精解[未登錄]  回復(fù)  更多評(píng)論   

          2011-02-12 10:57 by jeff
          寫得很不錯(cuò),比一般人寫得好多 了.樓主是真會(huì)的人.頂

          # re: Java線程安全精解  回復(fù)  更多評(píng)論   

          2011-04-06 15:54 by su30mmkx
          文中
          “1. 不用做什么,只一個(gè)獨(dú)立的變量,任何時(shí)候它都是原子、一致的。”
          反省一下是不是有問題。 本人覺得這種情況下也不是線程安全的。
          主站蜘蛛池模板: 巧家县| 西藏| 阳谷县| 江永县| 灵武市| 枣阳市| 哈巴河县| 新安县| 合阳县| 咸宁市| 南江县| 通海县| 延安市| 永兴县| 嘉鱼县| 眉山市| 年辖:市辖区| 平邑县| 磴口县| 洞头县| 昭苏县| 泸西县| 象山县| 乐东| 青州市| 凤山市| 泰安市| 崇信县| 荣成市| 平凉市| 慈溪市| 灵川县| 石林| 固阳县| 新化县| 冷水江市| 台湾省| 格尔木市| 西盟| 探索| 酉阳|