版權聲明:本文由吳仙傑創做整理,轉載請註明出處:http://www.javashuo.com/article/p-flychtvo-gh.htmljava
在 Java 多線程編程中,咱們常須要考慮線程安全問題,其中關鍵字 synchronized
在線程同步中就扮演了很是重要的做用。shell
下面就對 synchronized
進行詳細的示例講解,其中本文構建 thread
的寫法是採用 Java 8 新增的 Lambda 表達式。若是你對 Lambda 表達式還不瞭解,能夠查看我以前的文章《Java 8 Lambda 表達式詳解》。編程
首先咱們明確一點,synchronized
鎖的不是代碼,鎖的都是對象。segmentfault
鎖的對象有如下幾種:安全
同步非靜態方法(synchronized method
),鎖是當前對象的實例對象。多線程
同步靜態方法(synchronized static method
),鎖是當前對象的類對象(Class 對象)。異步
同步代碼塊一(synchronized (this)
,synchronized (類實例對象)
),鎖是小括號 ()
中的實例對象。性能
同步代碼塊二(synchronized (類.class)
),鎖是小括號 ()
中的類對象(Class 對象)。優化
1)實例對象鎖,不一樣的實例擁有不一樣的實例對象鎖,因此對於同一個實例對象,在同一時刻只有一個線程能夠訪問這個實例對象的同步方法;不一樣的實例對象,不能保證多線程的同步操做。this
2)類對象鎖(全局鎖),在 JVM 中一個類只有一個與之對應的類對象,因此在同一時刻只有一個線程能夠訪問這個類的同步方法。
同步非靜態方法,實際上鎖定的是當前對象的實例對象。在同一時刻只有一個線程能夠訪問該實例的同步方法,但對於多個實例的同步方法,不一樣實例之間對同步方法的訪問是不受同步影響(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
從上面的運行結果,咱們發現對 Bank
中 money
的操做並無同步,synchronized
失效了?
這是由於實例對象鎖,只對同一個實例生效,對同一個對象的不一樣實例不保證同步。
修改上述代碼,實現同步操做。這裏將有兩種方案:只實例化一個或在方法塊中的鎖定類對象。
方案1、多個線程只對同一個實例對象操做:
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 ...
能夠看到,結果正確執行。由於對於同一個實例對象,各線程之間訪問其中的同步方法是互斥的。
方案2、在方法塊中鎖定類對象:
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
思考:從結果中咱們發現,線程是同步操做了,但爲何在咱們的 money
怎麼才 1200 啊?
要回答上面問題也很簡單,首先線程是同步操做了,這個沒有疑問,說明咱們的全局鎖生效了,那爲何錢少了,由於咱們這裏 mew
了三個對象,三個對象都有各自的 money
,他們並不共享,因此最後都是 1200,最終一共仍是增長了 6000,錢一點沒有少喔。
那有沒有辦法,讓這些線程間共享 money
呢?方法很簡單,只要設置 money
爲 static
便可。
對於一個方法,可能包含多個操做部分,而每一個操做部分的消耗各不相同,並且並非全部的操做都是須要同步控制的,那麼,是否能夠將那些影響效率,又不須要同步操做的內容,提取到同步代碼塊外呢?
請看如下示例:
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 小明--和錢無關,不須要同步控制的操做 小紅--和錢無關,不須要同步控制的操做 小剛--和錢無關,不須要同步控制的操做
這時發現,各線程雖然都有本身的實例化對象,但其中操做 money
的部分是同步的,對於與 money
無關的操做則又是異步的。
結論:能夠經過減小同步區域來優化同步代碼塊。
咱們知道同步的對象不是實例對象就是類對象。如今假設一個類有多個同步方法,那麼當某個線程進入其中一個同步方法時,這個類的其它同步方法也會被鎖住,形成其它與當前鎖定操做的同步方法毫無關係的同步方法也被鎖住,最後的結果就是影響了整個多線程執行的性能,使本來不須要互斥的方法也都進行了互斥操做。好比:
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
咱們發現,兩個線程間同步被取消了,性能問題也解決了。
總結:能夠建立不一樣同步方法的不一樣同步鎖(減少鎖的範圍)來優化同步代碼塊。
同步靜態方法,鎖的是類對象而不是某個實例對象,因此能夠理解爲對於靜態方法的鎖是全局的鎖,同步也是全局的同步。
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 小剛--存入後銀行餘額爲:1600
同步鎖 synchronized
要點:
synchronized
鎖的不是代碼,鎖的都是對象。
實例對象鎖:同步非靜態方法(synchronized method
),同步代碼塊(synchronized (this)
,synchronized (類實例對象)
)。
類對象(Class 對象)鎖:同步靜態方法(synchronized static method
),同步代碼塊(synchronized (類.class)
)。
相同對象的不一樣的實例擁有不一樣的實例對象鎖,但類對象鎖(全局鎖)有僅只有一個。
優化同步代碼塊的方式有,減小同步區域或減少鎖的範圍。