成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

搞定高并發(fā),豈能不懂Synchronized底層原理?

開發(fā) 架構(gòu) 開發(fā)工具
Synchronized 是 Java 中解決并發(fā)問題的一種最常用的方法,也是最簡單的一種方法。本文作者將全面剖析 Synchronized 的底層原理。

Synchronized 是 Java 中解決并發(fā)問題的一種最常用的方法,也是最簡單的一種方法。本文作者將全面剖析 Synchronized 的底層原理。

Synchronized 的基本使用

Synchronized 的作用主要有三個:

  • 確保線程互斥的訪問同步代碼
  • 保證共享變量的修改能夠及時可見
  • 有效解決重排序問題

從語法上講,Synchronized 總共有三種用法:

  • 修飾普通方法
  • 修飾靜態(tài)方法
  • 修飾代碼塊

接下來我就通過幾個例子程序來說明一下這三種使用方式(為了便于比較,三段代碼除了 Synchronized 的使用方式不同以外,其他基本保持一致)。

沒有同步的情況

代碼段 1:

  1. package com.paddx.test.concurrent; 
  2.  
  3. public class SynchronizedTest { 
  4.   public void method1(){ 
  5.  
  6.     System.out.println("Method 1 start"); 
  7.     try { 
  8.       System.out.println("Method 1 execute"); 
  9.       Thread.sleep(3000); 
  10.     } catch (InterruptedException e) { 
  11.        e.printStackTrace(); 
  12.     } 
  13.     System.out.println("Method 1 end"); 
  14.   } 
  15.  
  16.   public void method2(){ 
  17.     System.out.println("Method 2 start"); 
  18.     try { 
  19.       System.out.println("Method 2 execute"); 
  20.       Thread.sleep(1000); 
  21.     } catch (InterruptedException e) { 
  22.       e.printStackTrace(); 
  23.     } 
  24.     System.out.println("Method 2 end"); 
  25.   } 
  26.  
  27.   public static void main(String[] args) { 
  28.     final SynchronizedTest test = new SynchronizedTest(); 
  29.  
  30.     new Thread(new Runnable() { 
  31.       @Override 
  32.       public void run() { 
  33.         test.method1(); 
  34.       } 
  35.     }).start(); 
  36.  
  37.     new Thread(new Runnable() { 
  38.       @Override 
  39.       public void run() { 
  40.         test.method2(); 
  41.       } 
  42.     }).start(); 
  43.   } 

執(zhí)行結(jié)果如下,線程 1 和線程 2 同時進入執(zhí)行狀態(tài),線程 2 執(zhí)行速度比線程 1 快,所以線程 2 先執(zhí)行完成。

這個過程中線程 1 和線程 2 是同時執(zhí)行的:

  1. Method 1 start 
  2. Method 1 execute 
  3. Method 2 start 
  4. Method 2 execute 
  5. Method 2 end 
  6. Method 1 end 

對普通方法同步

代碼段 2:

  1. package com.paddx.test.concurrent; 
  2.  
  3. public class SynchronizedTest { 
  4.   public synchronized void method1(){ 
  5.     System.out.println("Method 1 start"); 
  6.     try { 
  7.       System.out.println("Method 1 execute"); 
  8.       Thread.sleep(3000); 
  9.     } catch (InterruptedException e) { 
  10.       e.printStackTrace(); 
  11.     } 
  12.     System.out.println("Method 1 end"); 
  13.   } 
  14.  
  15.   public synchronized void method2(){ 
  16.     System.out.println("Method 2 start"); 
  17.     try { 
  18.       System.out.println("Method 2 execute"); 
  19.       Thread.sleep(1000); 
  20.     } catch (InterruptedException e) { 
  21.       e.printStackTrace(); 
  22.     } 
  23.     System.out.println("Method 2 end"); 
  24.   } 
  25.  
  26.   public static void main(String[] args) { 
  27.     final SynchronizedTest test = new SynchronizedTest(); 
  28.  
  29.     new Thread(new Runnable() { 
  30.       @Override 
  31.       public void run() { 
  32.         test.method1(); 
  33.       } 
  34.     }).start(); 
  35.  
  36.     new Thread(new Runnable() { 
  37.       @Override 
  38.       public void run() { 
  39.         test.method2(); 
  40.       } 
  41.     }).start(); 
  42.   } 

執(zhí)行結(jié)果如下,跟代碼段 1 比較,可以很明顯的看出,線程 2 需要等待線程 1 的 Method1 執(zhí)行完成才能開始執(zhí)行 Method2 方法。

  1. Method 1 start 
  2. Method 1 execute 
  3. Method 1 end 
  4. Method 2 start 
  5. Method 2 execute 
  6. Method 2 end 

靜態(tài)方法(類)同步

代碼段 3:

  1. package com.paddx.test.concurrent; 
  2.  
  3. public class SynchronizedTest { 
  4.   public static synchronized void method1(){ 
  5.     System.out.println("Method 1 start"); 
  6.     try { 
  7.       System.out.println("Method 1 execute"); 
  8.       Thread.sleep(3000); 
  9.     } catch (InterruptedException e) { 
  10.       e.printStackTrace(); 
  11.     } 
  12.     System.out.println("Method 1 end"); 
  13.   } 
  14.  
  15.   public static synchronized void method2(){ 
  16.     System.out.println("Method 2 start"); 
  17.     try { 
  18.       System.out.println("Method 2 execute"); 
  19.       Thread.sleep(1000); 
  20.     } catch (InterruptedException e) { 
  21.       e.printStackTrace(); 
  22.     } 
  23.     System.out.println("Method 2 end"); 
  24.   } 
  25.  
  26.   public static void main(String[] args) { 
  27.     final SynchronizedTest test = new SynchronizedTest(); 
  28.     final SynchronizedTest test2 = new SynchronizedTest(); 
  29.  
  30.     new Thread(new Runnable() { 
  31.       @Override 
  32.       public void run() { 
  33.         test.method1(); 
  34.       } 
  35.     }).start(); 
  36.  
  37.     new Thread(new Runnable() { 
  38.       @Override 
  39.       public void run() { 
  40.         test2.method2(); 
  41.       } 
  42.     }).start(); 
  43.   } 
  44.  } 

執(zhí)行結(jié)果如下,對靜態(tài)方法的同步本質(zhì)上是對類的同步(靜態(tài)方法本質(zhì)上是屬于類的方法,而不是對象上的方法)。

所以即使 Test 和 Test2 屬于不同的對象,但是它們都屬于 SynchronizedTest 類的實例。

所以也只能順序的執(zhí)行 Method1 和 Method2,不能并發(fā)執(zhí)行:

  1. Method 1 start 
  2. Method 1 execute 
  3. Method 1 end 
  4. Method 2 start 
  5. Method 2 execute 
  6. Method 2 end 

代碼塊同步

代碼段 4:

  1. package com.paddx.test.concurrent; 
  2.  
  3. public class SynchronizedTest { 
  4.   public void method1(){ 
  5.     System.out.println("Method 1 start"); 
  6.     try { 
  7.       synchronized (this) { 
  8.         System.out.println("Method 1 execute"); 
  9.         Thread.sleep(3000); 
  10.       } 
  11.     } catch (InterruptedException e) { 
  12.       e.printStackTrace(); 
  13.     } 
  14.     System.out.println("Method 1 end"); 
  15.   } 
  16.  
  17.   public void method2(){ 
  18.     System.out.println("Method 2 start"); 
  19.     try { 
  20.       synchronized (this) { 
  21.         System.out.println("Method 2 execute"); 
  22.         Thread.sleep(1000); 
  23.       } 
  24.     } catch (InterruptedException e) { 
  25.       e.printStackTrace(); 
  26.     } 
  27.     System.out.println("Method 2 end"); 
  28.   } 
  29.  
  30.   public static void main(String[] args) { 
  31.     final SynchronizedTest test = new SynchronizedTest(); 
  32.  
  33.     new Thread(new Runnable() { 
  34.       @Override 
  35.       public void run() { 
  36.         test.method1(); 
  37.       } 
  38.     }).start(); 
  39.  
  40.     new Thread(new Runnable() { 
  41.       @Override 
  42.       public void run() { 
  43.         test.method2(); 
  44.       } 
  45.     }).start(); 
  46.   } 

執(zhí)行結(jié)果如下,雖然線程 1 和線程 2 都進入了對應(yīng)的方法開始執(zhí)行,但是線程 2 在進入同步塊之前,需要等待線程 1 中同步塊執(zhí)行完成。

  1. Method 1 start 
  2. Method 1 execute 
  3. Method 2 start 
  4. Method 1 end 
  5. Method 2 execute 
  6. Method 2 end 

Synchronized 原理

如果對上面的執(zhí)行結(jié)果還有疑問,也先不用急,我們先來了解 Synchronized 的原理。

再回頭上面的問題就一目了然了。我們先通過反編譯下面的代碼來看看 Synchronized 是如何實現(xiàn)對代碼塊進行同步的:

  1. package com.paddx.test.concurrent; 
  2. public class SynchronizedMethod { 
  3.   public synchronized void method() { 
  4.     System.out.println("Hello World!"); 
  5.   } 

反編譯結(jié)果:

關(guān)于這兩條指令的作用,我們直接參考 JVM 規(guī)范中描述:

monitorenter :Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

  •  If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
  •  If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
  • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

這段話的大概意思為:每個對象有一個監(jiān)視器鎖(Monitor),當(dāng) Monitor 被占用時就會處于鎖定狀態(tài)。

線程執(zhí)行 Monitorenter 指令時嘗試獲取 Monitor 的所有權(quán),過程如下:

  • 如果 Monitor 的進入數(shù)為 0,則該線程進入 Monitor,然后將進入數(shù)設(shè)置為 1,該線程即為 Monitor 的所有者。
  • 如果線程已經(jīng)占有該 Monitor,只是重新進入,則進入 Monitor 的進入數(shù)加 1。
  • 如果其他線程已經(jīng)占用了 Monitor,則該線程進入阻塞狀態(tài),直到 Monitor 的進入數(shù)為 0,再重新嘗試獲取 Monitor 的所有權(quán)。

monitorexit:The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner.

Other threads that are blocking to enter the monitor are allowed to attempt to do so.

這段話的大概意思為:執(zhí)行 Monitorexit 的線程必須是 Objectref 所對應(yīng)的 Monitor 的所有者。

指令執(zhí)行時,Monitor 的進入數(shù)減 1,如果減 1 后進入數(shù)為 0,那線程退出 Monitor,不再是這個 Monitor 的所有者。

其他被這個 Monitor 阻塞的線程可以嘗試去獲取這個 Monitor 的所有權(quán)。

通過這兩段描述,我們應(yīng)該能很清楚的看出 Synchronized 的實現(xiàn)原理。

Synchronized 的語義底層是通過一個 Monitor 的對象來完成,其實 Wait/Notify 等方法也依賴于 Monitor 對象。

這就是為什么只有在同步的塊或者方法中才能調(diào)用 Wait/Notify 等方法,否則會拋出 java.lang.IllegalMonitorStateException 的異常。

我們再來看一下同步方法的反編譯結(jié)果,源代碼如下:

  1. package com.paddx.test.concurrent; 
  2.  
  3. public class SynchronizedMethod { 
  4.   public synchronized void method() { 
  5.     System.out.println("Hello World!"); 
  6.   } 

反編譯結(jié)果:

從反編譯的結(jié)果來看,方法的同步并沒有通過指令 Monitorenter 和 Monitorexit 來完成(理論上其實也可以通過這兩條指令來實現(xiàn))。不過相對于普通方法,其常量池中多了 ACC_SYNCHRONIZED 標(biāo)示符。

JVM 就是根據(jù)該標(biāo)示符來實現(xiàn)方法的同步的:當(dāng)方法調(diào)用時,調(diào)用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置。

如果設(shè)置了,執(zhí)行線程將先獲取 Monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放 Monitor。在方法執(zhí)行期間,其他任何線程都無法再獲得同一個 Monitor 對象。

其實本質(zhì)上沒有區(qū)別,只是方法的同步是一種隱式的方式來實現(xiàn),無需通過字節(jié)碼來完成。

運行結(jié)果解釋

有了對 Synchronized 原理的認(rèn)識,再來看上面的程序就可以迎刃而解了。

①代碼段 2 結(jié)果

雖然 Method1 和 Method2 是不同的方法,但是這兩個方法都進行了同步,并且是通過同一個對象去調(diào)用的。

所以調(diào)用之前都需要先去競爭同一個對象上的鎖(Monitor),也就只能互斥的獲取到鎖,因此,Method1 和 Method2 只能順序的執(zhí)行。

②代碼段 3 結(jié)果

雖然 Test 和 Test2 屬于不同對象,但是 Test 和 Test2 屬于同一個類的不同實例。

由于 Method1 和 Method2 都屬于靜態(tài)同步方法,所以調(diào)用的時候需要獲取同一個類上 Monitor(每個類只對應(yīng)一個 Class 對象),所以也只能順序的執(zhí)行。

③代碼段 4 結(jié)果

對于代碼塊的同步,實質(zhì)上需要獲取 Synchronized 關(guān)鍵字后面括號中對象的 Monitor。

由于這段代碼中括號的內(nèi)容都是 This,而 Method1 和 Method2 又是通過同一的對象去調(diào)用的,所以進入同步塊之前需要去競爭同一個對象上的鎖,因此只能順序執(zhí)行同步塊。

總結(jié)

Synchronized 是 Java 并發(fā)編程中最常用的用于保證線程安全的方式,其使用相對也比較簡單。

但是如果能夠深入了解其原理,對監(jiān)視器鎖等底層知識有所了解,一方面可以幫助我們正確的使用 Synchronized 關(guān)鍵字。

另一方面也能夠幫助我們更好的理解并發(fā)編程機制,有助于我們在不同的情況下選擇更優(yōu)的并發(fā)策略來完成任務(wù)。對平時遇到的各種并發(fā)問題,也能夠從容的應(yīng)對。

 

責(zé)任編輯:武曉燕 來源: 博客園
相關(guān)推薦

2021-01-08 08:34:09

Synchronize線程開發(fā)技術(shù)

2020-06-30 09:12:34

高并發(fā)薪資并發(fā)量

2024-03-15 15:12:27

關(guān)鍵字底層代碼

2022-12-26 09:27:48

Java底層monitor

2025-03-20 06:48:55

性能優(yōu)化JDK

2021-12-01 06:50:50

Docker底層原理

2022-10-28 10:23:27

Java多線程底層

2024-08-28 08:00:00

2017-02-27 10:43:07

Javasynchronize

2024-03-07 07:47:04

代碼塊Monitor

2019-08-26 08:36:09

負(fù)載均衡高可用Nginx

2025-01-03 09:36:22

Nginx高并發(fā)進程

2023-10-12 00:00:00

面試程序多線程

2019-10-31 10:08:15

Synchronize面試線程

2024-11-14 11:15:32

2022-04-13 14:43:05

JVM同步鎖Monitor 監(jiān)視

2017-12-06 16:28:48

Synchronize實現(xiàn)原理

2020-09-22 12:00:23

Javahashmap高并發(fā)

2025-06-30 00:00:00

2016-03-04 09:42:12

無線技術(shù)WiFi
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 精品国产综合 | 99视频在线| 亚洲一区 中文字幕 | 成人综合在线视频 | 成人在线一区二区 | 国产精品久久久久久久久免费软件 | 色频| 欧美一区免费 | 九九久久免费视频 | 日韩av啪啪网站大全免费观看 | 久久久www成人免费精品 | 中文字幕第二十页 | 亚洲精品欧美一区二区三区 | 色综合一区二区三区 | 欧美精品在线播放 | 日韩欧美视频 | 99视频在线 | 国产在线第一页 | 国产网站在线播放 | 久久y| 亚洲精品99 | 久久99精品久久久久久 | 欧美日韩视频 | 日韩在线看片 | 国产露脸对白88av | 国产精品视频免费观看 | 日韩中文字幕在线播放 | 91电影| 古装人性做爰av网站 | 狠狠躁躁夜夜躁波多野结依 | 亚洲电影免费 | 欧美日韩最新 | 国产欧美视频一区二区 | a欧美| 国产午夜精品一区二区三区在线观看 | 日韩中文一区 | 欧美日韩精品在线免费观看 | 欧美日韩亚洲一区 | 亚洲欧美精品 | 成人永久免费视频 | 亚洲有码转帖 |