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