synchronized
工做原理及使用小結爲確保共享變量不會出現併發問題,一般會對修改共享變量的代碼塊用
synchronized
加鎖,確保同一時刻只有一個線程在修改共享變量,從而避免併發問題java
本篇將集中在synchronized
關鍵字的工組原理以及使用方式上安全
以一個case進行分析,源碼以下多線程
public class SynchronizedDemo { public void method() { synchronized (this) { System.out.println("Method 1 start"); } } }
在加鎖的代碼塊, 多了一個 monitorenter
, monitorexit
併發
每一個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:性能
執行monitorexit的線程必須是objectref所對應的monitor的全部者。測試
鎖this
談到
synchronized
就不可避免的要說到鎖這個東西,基本上在網上能夠搜索到一大批的關於偏向鎖,輕量鎖,重量鎖的講解文檔,對這個東西基本上我也不太理解,多看幾篇博文以後,簡單的記錄一下操作系統
先拋一個結論: 輕量級鎖是爲了在線程交替執行同步塊時提升性能,而偏向鎖則是在只有一個線程執行同步塊時進一步提升性能線程
獲取過程code
釋放過程
「輕量級」是相對於使用操做系統互斥量來實現的傳統鎖而言的。
可是,首先須要強調一點的是,輕量級鎖並非用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減小傳統的重量級鎖使用產生的性能消耗。
在解釋輕量級鎖的執行過程以前,先明白一點,輕量級鎖所適應的場景是線程交替執行同步塊的狀況,若是存在同一時間訪問同一鎖的狀況,就會致使輕量級鎖膨脹爲重量級鎖
簡單來說,單線程時,使用偏向鎖,若是這個時候,又來了一個線程訪問這個代碼塊,那麼就要升級爲輕量鎖,若是這個線程在訪問代碼塊同時,又來了一個線程來訪問這個代碼塊,那麼就要升級爲重量鎖了。下面更多的顯示了這些變更時,標記位的隨之改變
修飾實例方法
多個線程訪問同一個實例的加鎖方法時,會出現鎖的競爭
修飾靜態方法
多個線程訪問類的加鎖方法時,會出現鎖的競爭
修飾代碼塊
多線程訪問到同一個代碼塊時,會出現競爭的問題
一個case: TestDemo方法定義以下
public class TestDemo { public synchronized void a() { // ... } public synchronized void b() { // ... } public static synchronized void c() { // ... } public static synchronized void d() { // ... } public void e() { // ... } public void f() { synchronized(this) { // .... } } public void g() { synchronized(this) { // .... } } }
對上面的問題,核心的一點就是synchronized
是否只做用於修飾的代碼塊or方法上
TestDemo的具體實現以下
public class TestDemo { public synchronized void a(String msg) { System.out.println(Thread.currentThread().getName() + ":a() before"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":a() after: " + msg); } public synchronized void b(String msg) { System.out.println(Thread.currentThread().getName() + ":b() before"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":b() after: " + msg); } public static synchronized void c(String msg) { System.out.println(Thread.currentThread().getName() + ":c() before"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":c() after: " + msg); } public static synchronized void d(String msg) { System.out.println(Thread.currentThread().getName() + ":d() before"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":d() after: " + msg); } public void e(String msg) { System.out.println(Thread.currentThread().getName() + ":e() before"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":e() after: " + msg); } public void f(String msg) { System.out.println(Thread.currentThread().getName() + ":f() before"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":f() after: " + msg); } public void g(String msg) { synchronized (this) { System.out.println(Thread.currentThread().getName() + ":a() before"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":a() after: " + msg); } } public void h(String msg) { synchronized (this) { System.out.println(Thread.currentThread().getName() + ":h() before"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":h() after: " + msg); } } }
測試一 實例加鎖方法的訪問測試
/** * 非靜態同步方法測試 */ private void nonStaticSynFun() throws InterruptedException { TestDemo testDemo = new TestDemo(); Thread thread1 = new Thread(()->testDemo.a("訪問同一加鎖方法"), "thread1"); Thread thread2 = new Thread(()->testDemo.a("訪問同一加鎖方法"), "thread2"); System.out.println("---兩個線程,訪問同一個加鎖方法開始---"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("---兩個線程,訪問同一個加鎖方法結束---\n"); // TestDemo testDemo2 = new TestDemo(); thread1 = new Thread(()->testDemo.a("訪問第一個實例同一加鎖方法"), "thread1"); thread2 = new Thread(()->testDemo2.a("訪問第二個實例同一加鎖方法"), "thread2"); System.out.println("---兩個線程,訪問兩個實例同一個加鎖方法開始---"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("---兩個線程,訪問兩個同一個加鎖方法結束---\n"); // thread1 = new Thread(()->testDemo.a("訪問兩個加鎖方法"), "thread1"); thread2 = new Thread(()->testDemo.b("訪問兩個加鎖方法"), "thread2"); System.out.println("---兩個線程,訪問兩個加鎖方法開始---"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("---兩個線程,訪問兩個加鎖方法結束---\n"); // thread1 = new Thread(()->testDemo.a("訪問加鎖實例方法"), "thread1"); thread2 = new Thread(()->TestDemo.c("訪問加鎖靜態方法"), "thread2"); System.out.println("---兩個線程,訪問實例和靜態加鎖方法開始---"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("---兩個線程,訪問實例和靜態加鎖方法結束---\n"); } @Test public void testNoStaticSynFun() throws InterruptedException { for(int i = 0; i < 2000; i++) { nonStaticSynFun(); } }
上面的測試case主要覆蓋:
輸出結果以下
---兩個線程,訪問同一個加鎖方法開始--- thread1:a() before thread1:a() after: 訪問同一加鎖方法 thread2:a() before thread2:a() after: 訪問同一加鎖方法 ---兩個線程,訪問同一個加鎖方法結束--- ---兩個線程,訪問兩個實例同一個加鎖方法開始--- thread1:a() before thread2:a() before thread2:a() after: 訪問第二個實例同一加鎖方法 thread1:a() after: 訪問第一個實例同一加鎖方法 ---兩個線程,訪問兩個同一個加鎖方法結束--- ---兩個線程,訪問兩個加鎖方法開始--- thread1:a() before thread1:a() after: 訪問兩個加鎖方法 thread2:b() before thread2:b() after: 訪問兩個加鎖方法 ---兩個線程,訪問兩個加鎖方法結束--- ---兩個線程,訪問實例和靜態加鎖方法開始--- thread1:a() before thread2:c() before thread2:c() after: 訪問加鎖靜態方法 thread1:a() after: 訪問加鎖實例方法 ---兩個線程,訪問實例和靜態加鎖方法結束---
驗證結果:
測試case二: 靜態加鎖方法測試
private void staticSynFun() throws InterruptedException { Thread thread1 = new Thread(() -> TestDemo.c("訪問加鎖靜態方法"), "thread1"); Thread thread2 = new Thread(() -> TestDemo.c("訪問加鎖靜態方法"), "thread2"); System.out.println("---兩個線程,訪問靜態加鎖方法開始---"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("---兩個線程,訪問靜態加鎖方法結束---\n"); // TestDemo testDemo1 = new TestDemo(), testDemo2 = new TestDemo(); thread1 = new Thread(() -> testDemo1.c("訪問加鎖靜態方法"), "thread1"); thread2 = new Thread(() -> testDemo2.d("訪問加鎖靜態方法"), "thread2"); Thread thread3 = new Thread(() -> testDemo1.a("訪問加鎖實例方法"), "thread3"); System.out.println("---兩個線程,訪問不一樣實例的靜態加鎖方法開始---"); thread1.start(); thread2.start(); thread3.start(); thread1.join(); thread2.join(); thread3.join(); System.out.println("---兩個線程,訪問不一樣實例的靜態加鎖方法結束---\n"); } @Test public void testStaticSynFunc() throws InterruptedException { for (int i = 0; i < 2000; i++) { staticSynFun(); } }
上面的測試主要覆蓋
輸出結果以下
---兩個線程,訪問靜態加鎖方法開始--- thread1:c() before thread1:c() after: 訪問加鎖靜態方法 thread2:c() before thread2:c() after: 訪問加鎖靜態方法 ---兩個線程,訪問靜態加鎖方法結束--- ---兩個線程,訪問不一樣實例的靜態加鎖方法開始--- thread1:c() before thread3:a() before thread1:c() after: 訪問加鎖靜態方法 thread2:d() before thread3:a() after: 訪問加鎖實例方法 thread2:d() after: 訪問加鎖靜態方法 ---兩個線程,訪問不一樣實例的靜態加鎖方法結束---
驗證結果:
測試case三: 同步代碼塊
基本上和上面的相同,同步代碼塊分爲靜態同步代碼塊(共享類鎖);非靜態同步代碼塊(共享實例鎖)
synchronized
三中使用姿式,修飾靜態方法,實例方法,(靜態/非靜態)代碼塊synchronized
底層主要是經過偏向鎖,輕量級鎖和重量級鎖組合來實現線程同步的功能GG