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