最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問題

 更新時(shí)間:2016年06月15日 11:43:17   作者:可文分身  
Java的synchronized關(guān)鍵字用來進(jìn)行線程同步操作,然而這在使用中經(jīng)常會遇到一些問題,這里我們就來詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問題:

先看一段synchronized 的詳解:
synchronized 是 java語言的關(guān)鍵字,當(dāng)它用來修飾一個(gè)方法或者一個(gè)代碼塊的時(shí)候,能夠保證在同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼。

一、當(dāng)兩個(gè)并發(fā)線程訪問同一個(gè)對象object中的這個(gè)synchronized(this)同步代碼塊時(shí),一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。

二、然而,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。

三、尤其關(guān)鍵的是,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。

四、第三個(gè)例子同樣適用其它同步代碼塊。也就是說,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),它就獲得了這個(gè)object的對象鎖。結(jié)果,其它線程對該object對象所有同步代碼部分的訪問都被暫時(shí)阻塞。

五、以上規(guī)則對其它對象鎖同樣適用.
簡單來說, synchronized就是為當(dāng)前的線程聲明一個(gè)鎖, 擁有這個(gè)鎖的線程可以執(zhí)行區(qū)塊里面的指令, 其他的線程只能等待獲取鎖, 然后才能相同的操作.
這個(gè)很好用, 但是筆者遇到另一種比較奇葩的情況.
1. 在同一類中, 有兩個(gè)方法是用了synchronized關(guān)鍵字聲明
2. 在執(zhí)行完其中一個(gè)方法的時(shí)候, 需要等待另一個(gè)方法(異步線程回調(diào))也執(zhí)行完, 所以用了一個(gè)countDownLatch來做等待
3. 代碼解構(gòu)如下:

synchronized void a(){
 countDownLatch = new CountDownLatch(1);
 // do someing
 countDownLatch.await();
}

synchronized void b(){
   countDownLatch.countDown();
}

其中
a方法由主線程執(zhí)行, b方法由異步線程執(zhí)行后回調(diào)
執(zhí)行結(jié)果是:
主線程執(zhí)行 a方法后開始卡住, 不再往下做, 任你等多久都沒用.
這是一個(gè)很經(jīng)典的死鎖問題
a等待b執(zhí)行, 其實(shí)不要看b是回調(diào)的, b也在等待a執(zhí)行. 為什么呢? synchronized 起了作用.
一般來說, 我們要synchronized一段代碼塊的時(shí)候, 我們需要使用一個(gè)共享變量來鎖住, 比如:

byte[] mutex = new byte[0];

void a1(){
   synchronized(mutex){
     //dosomething
   }
}

void b1(){

   synchronized(mutex){
     // dosomething
   }

}

如果把a(bǔ)方法和b方法的內(nèi)容分別遷移到 a1和b1 方法的synchronized塊里面, 就很好理解了.
a1執(zhí)行完后會間接等待(countDownLatch)b1方法執(zhí)行.
然而由于 a1 中的mutex并沒有釋放, 就開始等待b1了, 這時(shí)候, 即使是異步的回調(diào)b1方法, 由于需要等待mutex釋放鎖, 所以b方法并不會執(zhí)行.
于是就引起了死鎖!
而這里的synchronized關(guān)鍵字放在方法前面, 起的作用就是一樣的. 只是java語言幫你隱去了mutex的聲明和使用而已. 同一個(gè)對象中的synchronized 方法用到的mutex是相同的, 所以即使是異步回調(diào), 也會引起死鎖, 所以要注意這個(gè)問題. 這種級別的錯(cuò)誤是屬于synchronized關(guān)鍵字使用不當(dāng). 不要亂用, 而且要用對.
那么這樣的 隱形的mutex 對象究竟是 什么呢?
很容易想到的就是 實(shí)例本身. 因?yàn)檫@樣就不用去定義新的對象了做鎖了. 為了證明這個(gè)設(shè)想, 可以寫一段程序來證明.
思路很簡單, 定義一個(gè)類, 有兩個(gè)方法, 一個(gè)方法聲明為 synchronized, 一個(gè)在 方法體里面使用synchronized(this), 然后啟動(dòng)兩個(gè)線程, 來分別調(diào)用這兩個(gè)方法, 如果兩個(gè)方法之間發(fā)生鎖競爭(等待)的話, 就可以說明 方法聲明的 synchronized 中的隱形的mutex其實(shí)就是 實(shí)例本身了.

public class MultiThreadSync {

  public synchronized void m1() throws InterruptedException{
     System. out.println("m1 call" );
     Thread. sleep(2000);
     System. out.println("m1 call done" );
  }

  public void m2() throws InterruptedException{
     synchronized (this ) {
       System. out.println("m2 call" );
       Thread. sleep(2000);
       System. out.println("m2 call done" );
     }
  }

  public static void main(String[] args) {
     final MultiThreadSync thisObj = new MultiThreadSync();

     Thread t1 = new Thread(){
       @Override
       public void run() {
         try {
           thisObj.m1();
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
       }
     };

     Thread t2 = new Thread(){
       @Override
       public void run() {
         try {
           thisObj.m2();
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
       }
     };

     t1.start();
     t2.start();

  }

}

結(jié)果輸出是:

m1 call
m1 call done
m2 call
m2 call done

說明方法m2的sync塊等待了m1的執(zhí)行. 這樣就可以證實(shí) 上面的設(shè)想了.
另外需要說明的是, 當(dāng)sync加在 static的方法上的時(shí)候, 由于是類級別的方法, 所以鎖住的對象是當(dāng)前類的class實(shí)例. 同樣也可以寫程序進(jìn)行證明.這里略.
所以方法的synchronized 關(guān)鍵字, 在閱讀的時(shí)候可以自動(dòng)替換為synchronized(this){}就很好理解了.

                    void method(){
void synchronized method(){         synchronized(this){
   // biz code                // biz code
}               ------>>>   }
                    }

由Synchronized的內(nèi)存可見性說開去
在Java中,我們都知道關(guān)鍵字synchronized可以用于實(shí)現(xiàn)線程間的互斥,但我們卻常常忘記了它還有另外一個(gè)作用,那就是確保變量在內(nèi)存的可見性 - 即當(dāng)讀寫兩個(gè)線程同時(shí)訪問同一個(gè)變量時(shí),synchronized用于確保寫線程更新變量后,讀線程再訪問該 變量時(shí)可以讀取到該變量最新的值。

比如說下面的例子:

public class NoVisibility {
  private static boolean ready = false;
  private static int number = 0;

  private static class ReaderThread extends Thread {
    @Override
    public void run() {
      while (!ready) {
        Thread.yield(); //交出CPU讓其它線程工作
      }
      System.out.println(number);
    }
  }

  public static void main(String[] args) {
    new ReaderThread().start();
    number = 42;
    ready = true;
  }
}

你認(rèn)為讀線程會輸出什么? 42? 在正常情況下是會輸出42. 但是由于重排序問題,讀線程還有可能會輸出0 或者什么都不輸出。

我們知道,編譯器在將Java代碼編譯成字節(jié)碼的時(shí)候可能會對代碼進(jìn)行重排序,而CPU在執(zhí)行機(jī)器指令的時(shí)候也可能會對其指令進(jìn)行重排序,只要重排序不會破壞程序的語義 -

在單一線程中,只要重排序不會影響到程序的執(zhí)行結(jié)果,那么就不能保證其中的操作一定按照程序?qū)懚ǖ捻樞驁?zhí)行,即使重排序可能會對其它線程產(chǎn)生明顯的影響。
這也就是說,語句"ready=true"的執(zhí)行有可能要優(yōu)先于語句"number=42"的執(zhí)行,這種情況下,讀線程就有可能會輸出number的默認(rèn)值0.

而在Java內(nèi)存模型下,重排序問題是會導(dǎo)致這樣的內(nèi)存的可見性問題的。在Java內(nèi)存模型下,每個(gè)線程都有它自己的工作內(nèi)存(主要是CPU的cache或寄存器),它對變量的操作都在自己的工作內(nèi)存中進(jìn)行,而線程之間的通信則是通過主存和線程的工作內(nèi)存之間的同步來實(shí)現(xiàn)的。

比如說,對于上面的例子而言,寫線程已經(jīng)成功的將number更新為42,ready更新為true了,但是很有可能寫線程只同步了number到主存中(可能是由于CPU的寫緩沖導(dǎo)致),導(dǎo)致后續(xù)的讀線程讀取的ready值一直為false,那么上面的代碼就不會輸出任何數(shù)值。

2016615113818467.png (548×359)

而如果我們使用了synchronized關(guān)鍵字來進(jìn)行同步,則不會存在這樣的問題,

public class NoVisibility {
  private static boolean ready = false;
  private static int number = 0;
  private static Object lock = new Object();

  private static class ReaderThread extends Thread {
    @Override
    public void run() {
      synchronized (lock) {
        while (!ready) {
          Thread.yield();
        }
        System.out.println(number);
      }
    }
  }

  public static void main(String[] args) {
    synchronized (lock) {
      new ReaderThread().start();
      number = 42;
      ready = true;
    }
  }
}

這個(gè)是因?yàn)镴ava內(nèi)存模型對synchronized語義做了以下的保證,

2016615113903229.png (376×297)

即當(dāng)ThreadA釋放鎖M時(shí),它所寫過的變量(比如,x和y,存在它工作內(nèi)存中的)都會同步到主存中,而當(dāng)ThreadB在申請同一個(gè)鎖M時(shí),ThreadB的工作內(nèi)存會被設(shè)置為無效,然后ThreadB會重新從主存中加載它要訪問的變量到它的工作內(nèi)存中(這時(shí)x=1,y=1,是ThreadA中修改過的最新的值)。通過這樣的方式來實(shí)現(xiàn)ThreadA到ThreadB的線程間的通信。

這實(shí)際上是JSR133定義的其中一條happen-before規(guī)則。JSR133給Java內(nèi)存模型定義以下一組happen-before規(guī)則,

  • 單線程規(guī)則:同一個(gè)線程中的每個(gè)操作都happens-before于出現(xiàn)在其后的任何一個(gè)操作。
  • 對一個(gè)監(jiān)視器的解鎖操作happens-before于每一個(gè)后續(xù)對同一個(gè)監(jiān)視器的加鎖操作。
  • 對volatile字段的寫入操作happens-before于每一個(gè)后續(xù)的對同一個(gè)volatile字段的讀操作。
  • Thread.start()的調(diào)用操作會happens-before于啟動(dòng)線程里面的操作。
  • 一個(gè)線程中的所有操作都happens-before于其他線程成功返回在該線程上的join()調(diào)用后的所有操作。
  • 一個(gè)對象構(gòu)造函數(shù)的結(jié)束操作happens-before與該對象的finalizer的開始操作。
  • 傳遞性規(guī)則:如果A操作happens-before于B操作,而B操作happens-before與C操作,那么A動(dòng)作happens-before于C操作。

實(shí)際上這組happens-before規(guī)則定義了操作之間的內(nèi)存可見性,如果A操作happens-before B操作,那么A操作的執(zhí)行結(jié)果(比如對變量的寫入)必定在執(zhí)行B操作時(shí)可見。

為了更加深入的了解這些happens-before規(guī)則,我們來看一個(gè)例子:

//線程A,B共同訪問的代碼
Object lock = new Object();
int a=0;
int b=0;
int c=0;

//線程A,調(diào)用如下代碼
synchronized(lock){
  a=1; //1
  b=2; //2
} //3
c=3; //4


//線程B,調(diào)用如下代碼
synchronized(lock){ //5
  System.out.println(a); //6
  System.out.println(b); //7
  System.out.println(c); //8
}

我們假設(shè)線程A先運(yùn)行,分別給a,b,c三個(gè)變量進(jìn)行賦值(注:變量a,b的賦值是在同步語句塊中進(jìn)行的),然后線程B再運(yùn)行,分別讀取出這三個(gè)變量的值并打印出來。那么線程B打印出來的變量a,b,c的值分別是多少?

根據(jù)單線程規(guī)則,在A線程的執(zhí)行中,我們可以得出1操作happens before于2操作,2操作happens before于3操作,3操作happens before于4操作。同理,在B線程的執(zhí)行中,5操作happens before于6操作,6操作happens before于7操作,7操作happens before于8操作。而根據(jù)監(jiān)視器的解鎖和加鎖原則,3操作(解鎖操作)是happens before 5操作的(加鎖操作),再根據(jù)傳遞性 規(guī)則我們可以得出,操作1,2是happens before 操作6,7,8的。

則根據(jù)happens-before的內(nèi)存語義,操作1,2的執(zhí)行結(jié)果對于操作6,7,8是可見的,那么線程B里,打印的a,b肯定是1和2. 而對于變量c的操作4,和操作8. 我們并不能根據(jù)現(xiàn)有的happens before規(guī)則推出操作4 happens before于操作8. 所以在線程B中,訪問的到c變量有可能還是0,而不是3.

相關(guān)文章

最新評論

乌拉特后旗| 全州县| 许昌县| 新源县| 姜堰市| 资源县| 喀喇沁旗| 合山市| 哈巴河县| 高雄县| 仲巴县| 斗六市| 罗甸县| 宁远县| 应城市| 阳西县| 闽侯县| 汤阴县| 鹰潭市| 杭州市| 陇南市| 新龙县| 噶尔县| 长岛县| 齐齐哈尔市| 轮台县| 惠来县| 清流县| 澳门| 平南县| 西乡县| 长丰县| 买车| 金门县| 博白县| 家居| 常州市| 华池县| 四子王旗| 湛江市| 西昌市|