激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務(wù)器之家 - 編程語言 - JAVA教程 - 詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問題

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

2020-05-17 12:30可文分身 JAVA教程

Java的synchronized關(guān)鍵字用來進行線程同步操作,然而這在使用中經(jīng)常會遇到一些問題,這里我們就來詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問題:

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

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

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

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

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

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

?
1
2
3
4
5
6
7
8
9
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方法后開始卡住, 不再往下做, 任你等多久都沒用.
這是一個很經(jīng)典的死鎖問題
a等待b執(zhí)行, 其實不要看b是回調(diào)的, b也在等待a執(zhí)行. 為什么呢? synchronized 起了作用.
一般來說, 我們要synchronized一段代碼塊的時候, 我們需要使用一個共享變量來鎖住, 比如:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
byte[] mutex = new byte[0];
 
void a1(){
   synchronized(mutex){
     //dosomething
   }
}
 
void b1(){
 
   synchronized(mutex){
     // dosomething
   }
 
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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é)果輸出是:

?
1
2
3
4
m1 call
m1 call done
m2 call
m2 call done

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

?
1
2
3
4
5
                    void method(){
void synchronized method(){         synchronized(this){
   // biz code                // biz code
}               ------>>>   }
                    }

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

比如說下面的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
  }
}

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

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

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

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

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
    }
  }
}

這個是因為Java內(nèi)存模型對synchronized語義做了以下的保證,

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

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

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

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//線程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先運行,分別給a,b,c三個變量進行賦值(注:變量a,b的賦值是在同步語句塊中進行的),然后線程B再運行,分別讀取出這三個變量的值并打印出來。那么線程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.

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产99久久久国产精品下药 | 正在播放91视频 | 日韩a毛片免费观看 | 鸳鸯谱在线观看高清 | 精品国产观看 | 国产污污视频 | xxxxxx性| 亚洲极色| 99视频观看| 最新中文字幕第一页视频 | 亚洲天堂岛国片 | 中文字幕免费看 | 欧美a视频在线观看 | 久久久久久久爱 | 中国女人内谢8xxxxxx在 | 久久久久国产一区二区三区不卡 | 日韩视频区 | 国产成人在线看 | 一级黄色片在线看 | 免费看国产 | 国产精品99久久久久久久 | 中文字幕欧美日韩 | 二区视频 | 国产精品自拍啪啪 | 女人解衣喂奶电影 | 毛片a区| 人人舔人人舔 | 亚洲天堂在线电影 | 久久精品亚洲精品国产欧美kt∨ | 亚洲伊人色欲综合网 | 得得啪在线| 永久久久 | 姑娘第5集高清在线观看 | av在线免费播放网站 | 91精品国产成人 | 午夜av男人的天堂 | 91精品国产91热久久久做人人 | av在线浏览| 国内毛片视频 | 亚洲人成中文字幕在线观看 | 毛片网站视频 |