摘要:同步代碼塊二類,鎖是小括號中的類對象對象。因為對于同一個實例對象,各線程之間訪問其中的同步方法是互斥的。優(yōu)化同步代碼塊的方式有,減少同步區(qū)域或減小鎖的范圍。
1. 引言版權聲明:本文由吳仙杰創(chuàng)作整理,轉載請注明出處:https://segmentfault.com/a/1190000009225706
在 Java 多線程編程中,我們常需要考慮線程安全問題,其中關鍵字 synchronized 在線程同步中就扮演了非常重要的作用。
下面就對 synchronized 進行詳細的示例講解,其中本文構建 thread 的寫法是采用 Java 8 新增的 Lambda 表達式。如果你對 Lambda 表達式還不了解,可以查看我之前的文章《Java 8 Lambda 表達式詳解》。
2. synchronized 鎖的是什么首先我們明確一點,synchronized 鎖的不是代碼,鎖的都是對象。
鎖的對象有以下幾種:
同步非靜態(tài)方法(synchronized method),鎖是當前對象的實例對象。
同步靜態(tài)方法(synchronized static method),鎖是當前對象的類對象(Class 對象)。
同步代碼塊一(synchronized (this),synchronized (類實例對象)),鎖是小括號 () 中的實例對象。
同步代碼塊二(synchronized (類.class)),鎖是小括號 () 中的類對象(Class 對象)。
2.1 實例對象鎖與類對象鎖1)實例對象鎖,不同的實例擁有不同的實例對象鎖,所以對于同一個實例對象,在同一時刻只有一個線程可以訪問這個實例對象的同步方法;不同的實例對象,不能保證多線程的同步操作。
2)類對象鎖(全局鎖),在 JVM 中一個類只有一個與之對應的類對象,所以在同一時刻只有一個線程可以訪問這個類的同步方法。
3. 示例分析 3.1 同步非靜態(tài)實例方法同步非靜態(tài)方法,實際上鎖定的是當前對象的實例對象。在同一時刻只有一個線程可以訪問該實例的同步方法,但對于多個實例的同步方法,不同實例之間對同步方法的訪問是不受同步影響(synchronized 同步失效)。
首先我們嘗試下,synchronized 同步失敗的情況:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Bank xGBank = new Bank(); Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小剛"); Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小紅"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private int money = 1000; public synchronized void deposit(Bank bank, int money) { // synchronized (this) { // 同步方法塊(實例對象) // synchronized (bank) { // 同步方法塊(實例對象) String threadName = Thread.currentThread().getName(); System.out.println(threadName + "--當前銀行余額為:" + this.money); this.money += money; System.out.println(threadName + "--存入后銀行余額為:" + this.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // } } }
運行結果:
小明--當前銀行余額為:1000 小剛--當前銀行余額為:1000 小明--存入后銀行余額為:1200 小紅--當前銀行余額為:1000 小剛--存入后銀行余額為:1200 小紅--存入后銀行余額為:1200
從上面的運行結果,我們發(fā)現(xiàn)對 Bank 中 money 的操作并沒有同步,synchronized 失效了?
這是因為實例對象鎖,只對同一個實例生效,對同一個對象的不同實例不保證同步。
修改上述代碼,實現(xiàn)同步操作。這里將有兩種方案:只實例化一個或在方法塊中的鎖定類對象。
方案一、多個線程只對同一個實例對象操作:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); // Bank xGBank = new Bank(); // Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小剛"); Thread xHThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小紅"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private int money = 1000; public synchronized void deposit(Bank bank, int money) { // synchronized (this) { // 同步方法塊(實例對象) // synchronized (bank) { // 同步方法塊(實例對象) String threadName = Thread.currentThread().getName(); System.out.println(threadName + "--當前銀行余額為:" + this.money); this.money += money; System.out.println(threadName + "--存入后銀行余額為:" + this.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // } } }
運行結果:
小明--當前銀行余額為:1000 小明--存入后銀行余額為:1200 小紅--當前銀行余額為:1200 小紅--存入后銀行余額為:1400 小剛--當前銀行余額為:1400 小剛--存入后銀行余額為:1600 ...
可以看到,結果正確執(zhí)行。因為對于同一個實例對象,各線程之間訪問其中的同步方法是互斥的。
方案二、在方法塊中鎖定類對象:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Bank xGBank = new Bank(); Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小剛"); Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小紅"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private int money = 1000; public void deposit(Bank bank, int money) { synchronized (Bank.class) { // 全局鎖 String threadName = Thread.currentThread().getName(); System.out.println(threadName + "--當前銀行余額為:" + this.money); this.money += money; System.out.println(threadName + "--存入后銀行余額為:" + this.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
運行結果:
小明--當前銀行余額為:1000 小明--存入后銀行余額為:1200 小紅--當前銀行余額為:1000 小紅--存入后銀行余額為:1200 小剛--當前銀行余額為:1000 小剛--存入后銀行余額為:1200
思考:從結果中我們發(fā)現(xiàn),線程是同步操作了,但為什么在我們的 money 怎么才 1200 ?。?/p>
要回答上面問題也很簡單,首先線程是同步操作了,這個沒有疑問,說明我們的全局鎖生效了,那為什么錢少了,因為我們這里 mew 了三個對象,三個對象都有各自的 money,他們并不共享,所以最后都是 1200,最終一共還是增加了 6000,錢一點沒有少喔。
那有沒有辦法,讓這些線程間共享 money 呢?方法很簡單,只要設置 money 為 static 即可。
3.1.1 對同步代碼塊優(yōu)化的思考對于一個方法,可能包含多個操作部分,而每個操作部分的消耗各不相同,而且并不是所有的操作都是需要同步控制的,那么,是否可以將那些影響效率,又不需要同步操作的內容,提取到同步代碼塊外呢?
請看以下示例:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Bank xGBank = new Bank(); Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小剛"); Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小紅"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private int money = 1000; public void deposit(Bank bank, int money) { String threadName = Thread.currentThread().getName(); synchronized (Bank.class) { // 同步方法塊(實例對象) System.out.println(threadName + "--當前銀行余額為:" + this.money); this.money += money; System.out.println(threadName + "--存入后銀行余額為:" + this.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } try { // 假設這里是非常耗時,并且不需要同步控制的操作 Thread.sleep(2000); System.out.println(threadName + "--和錢無關,不需要同步控制的操作"); } catch (InterruptedException e) { e.printStackTrace(); } } }
運行結果:
小明--當前銀行余額為:1000 小明--存入后銀行余額為:1200 小紅--當前銀行余額為:1000 小紅--存入后銀行余額為:1200 小剛--當前銀行余額為:1000 小剛--存入后銀行余額為:1200 小明--和錢無關,不需要同步控制的操作 小紅--和錢無關,不需要同步控制的操作 小剛--和錢無關,不需要同步控制的操作
這時發(fā)現(xiàn),各線程雖然都有自己的實例化對象,但其中操作 money 的部分是同步的,對于與 money 無關的操作則又是異步的。
結論:可以通過減少同步區(qū)域來優(yōu)化同步代碼塊。
3.1.2 對同步代碼塊優(yōu)化的思考(進階)我們知道同步的對象不是實例對象就是類對象?,F(xiàn)在假設一個類有多個同步方法,那么當某個線程進入其中一個同步方法時,這個類的其它同步方法也會被鎖住,造成其它與當前鎖定操作的同步方法毫無關系的同步方法也被鎖住,最后的結果就是影響了整個多線程執(zhí)行的性能,使原本不需要互斥的方法也都進行了互斥操作。比如:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(xMBank::showInfo, "小剛"); xMThread.start(); xGThread.start(); } } class Bank { private int money = 1000; public void deposit(Bank bank, int money) { long begin = System.currentTimeMillis(); String threadName = Thread.currentThread().getName(); synchronized (this) { // 同步方法塊(實例對象) this.money += money; try { System.out.println(threadName + "--當前銀行余額為:" + this.money); // 模擬一個非常耗時的操作 Thread.sleep(5000); System.out.println(threadName + "--存入后銀行余額為:" + this.money); long end = System.currentTimeMillis(); System.out.println(threadName + "--存入耗時:" + (end - begin)); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 一個與資金操作沒有任務關系的同步方法 */ public void showInfo() { long begin = System.currentTimeMillis(); String threadName = Thread.currentThread().getName(); synchronized (this) { try { System.out.println(threadName + "--開始查看銀行信息"); Thread.sleep(5000); System.out.println(threadName + "--銀行詳細信息..."); long end = System.currentTimeMillis(); System.out.println(threadName + "--查看耗時:" + (end - begin)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
運行結果:
小明--當前銀行余額為:1200 小明--存入后銀行余額為:1200 小明--存入耗時:5000 小剛--開始查看銀行信息 小剛--銀行詳細信息... 小剛--查看耗時:10000
從運行結果中,我們看到小剛這個線程平白無故多等了 5 秒鐘,嚴重影響了線程性能。
針對上面的情況,我們可以采用多個實例對象鎖的方案解決,比如:
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(xMBank::showInfo, "小剛"); xMThread.start(); xGThread.start(); } } class Bank { private int money = 1000; private final Object syncDeposit = new Object(); // 同步鎖 private final Object syncShowInfo = new Object(); // 同步鎖 public void deposit(Bank bank, int money) { long begin = System.currentTimeMillis(); String threadName = Thread.currentThread().getName(); synchronized (this.syncDeposit) { // 同步方法塊(實例對象) this.money += money; try { System.out.println(threadName + "--當前銀行余額為:" + this.money); // 模擬一個非常耗時的操作 Thread.sleep(5000); System.out.println(threadName + "--存入后銀行余額為:" + this.money); long end = System.currentTimeMillis(); System.out.println(threadName + "--存入耗時:" + (end - begin)); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 一個與資金操作沒有任務關系的同步方法 */ public void showInfo() { long begin = System.currentTimeMillis(); String threadName = Thread.currentThread().getName(); synchronized (this.syncShowInfo) { try { System.out.println(threadName + "--開始查看銀行信息"); Thread.sleep(5000); System.out.println(threadName + "--銀行詳細信息..."); long end = System.currentTimeMillis(); System.out.println(threadName + "--查看耗時:" + (end - begin)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
運行結果:
小剛--開始查看銀行信息 小明--當前銀行余額為:1200 小剛--銀行詳細信息... 小明--存入后銀行余額為:1200 小明--存入耗時:5000 小剛--查看耗時:5000
我們發(fā)現(xiàn),兩個線程間同步被取消了,性能問題也解決了。
總結:可以創(chuàng)建不同同步方法的不同同步鎖(減小鎖的范圍)來優(yōu)化同步代碼塊。
3.2 同步靜態(tài)方法同步靜態(tài)方法,鎖的是類對象而不是某個實例對象,所以可以理解為對于靜態(tài)方法的鎖是全局的鎖,同步也是全局的同步。
package com.wuxianjiezh.demo.threadpool; public class MainTest { public static void main(String[] args) { Bank xMBank = new Bank(); Bank xGBank = new Bank(); Bank xHBank = new Bank(); Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明"); Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小剛"); Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小紅"); xMThread.start(); xGThread.start(); xHThread.start(); } } class Bank { private static int money = 1000; public synchronized static void deposit(Bank bank, int money) { // synchronized (Bank.class) { // 全局鎖 String threadName = Thread.currentThread().getName(); System.out.println(threadName + "--當前銀行余額為:" + Bank.money); Bank.money += money; System.out.println(threadName + "--存入后銀行余額為:" + Bank.money); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // } } }
運行結果:
小明--當前銀行余額為:1000 小明--存入后銀行余額為:1200 小紅--當前銀行余額為:1200 小紅--存入后銀行余額為:1400 小剛--當前銀行余額為:1400 小剛--存入后銀行余額為:16004. 總結
同步鎖 synchronized 要點:
synchronized 鎖的不是代碼,鎖的都是對象。
實例對象鎖:同步非靜態(tài)方法(synchronized method),同步代碼塊(synchronized (this),synchronized (類實例對象))。
類對象(Class 對象)鎖:同步靜態(tài)方法(synchronized static method),同步代碼塊(synchronized (類.class))。
相同對象的不同的實例擁有不同的實例對象鎖,但類對象鎖(全局鎖)有僅只有一個。
優(yōu)化同步代碼塊的方式有,減少同步區(qū)域或減小鎖的范圍。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/69948.html
摘要:基本使用同步代碼塊同步代碼塊延時秒,方便后面測試作用代碼塊時,方法中的,是指調用該方法的對象。那么這個時候使用關鍵字就需要注意了推薦使用同步代碼塊,同步的代碼塊中傳入外部定義的一個變量。 簡述 計算機單線程在執(zhí)行任務時,是嚴格按照程序的代碼邏輯,按照順序執(zhí)行的。因此單位時間內能執(zhí)行的任務數(shù)量有限。為了能在相同的時間內能執(zhí)行更多的任務,就必須采用多線程的方式來執(zhí)行(注意:多線程模式無法減...
摘要:但是單核我們還是要應用多線程,就是為了防止阻塞。多線程可以防止這個問題,多條線程同時運行,哪怕一條線程的代碼執(zhí)行讀取數(shù)據阻塞,也不會影響其它任務的執(zhí)行。 1、多線程有什么用?一個可能在很多人看來很扯淡的一個問題:我會用多線程就好了,還管它有什么用?在我看來,這個回答更扯淡。所謂知其然知其所以然,會用只是知其然,為什么用才是知其所以然,只有達到知其然知其所以然的程度才可以說是把一個知識點...
摘要:每個對象只有一個鎖與之相關聯(lián)。實現(xiàn)同步則是以系統(tǒng)開銷作為代價,甚至可能造成死鎖,所以盡量避免濫用。這種機制確保了同一時刻該類實例,所有聲明為的函數(shù)中只有一個方法處于可執(zhí)行狀態(tài),從而有效避免了類成員變量訪問沖突。 synchronized是JAVA語言的一個關鍵字,使用 synchronized 來修飾方法或代碼塊的時候,能夠保證多個線程中最多只有一個線程執(zhí)行該段代碼 ... 概述 ...
摘要:本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發(fā)訪問,線程間通信,的使用,定時器,單例模式,以及線程狀態(tài)與線程組。源碼采用構建,多線程這部分源碼位于模塊中。通知可能等待該對象的對象鎖的其他線程。 本文對多線程基礎知識進行梳理,主要包括多線程的基本使用,對象及變量的并發(fā)訪問,線程間通信,lock的使用,定時器,單例模式,以及線程狀態(tài)與線程組。 寫在前面 花了一周時...
閱讀 996·2021-11-23 09:51
閱讀 2710·2021-08-23 09:44
閱讀 670·2019-08-30 15:54
閱讀 1443·2019-08-30 13:53
閱讀 3118·2019-08-29 16:54
閱讀 2536·2019-08-29 16:26
閱讀 1205·2019-08-29 13:04
閱讀 2328·2019-08-26 13:50