1. 程式人生 > >Java同步方法:synchronized到底鎖住了誰?

Java同步方法:synchronized到底鎖住了誰?

[TOC] ## 前言 相信不少同學在上完Java課後,對於執行緒同步部分的實戰,都會感到不知其然。 比如上課做實驗的時候,按著老師的實驗指導書中的描述完成了多執行緒的同步操作,就感覺自己已經掌握這個知識點了,實際運用中再次手足無措,就像我一樣。 這裡提問一下:synchronized對方法修飾,在別處呼叫這個方法時,誰被鎖定了呢?另外,在新建執行緒中使用synchronized(this){ }結構時,如: ```java void methodA() { new Thread(() -> { synchronized (this) { this.methodB(); } }).start(); } ``` 這個被鎖的this又是誰呢? 這篇博文來詳細介紹一下執行緒同步中涉及synchronized修飾的兩種用法:同步方法和同步程式碼塊。 才不會說這篇是我對一個專案程式碼中的執行緒同步機制感到迷惑而搜資料寫的筆記( ## 同步方法 先開始介紹synchronized修飾符本身的特性: 1. synchronized關鍵字**不能**被繼承 即父類方法是同步方法 子類方法繼承後預設不是同步方法 1. synchronized**不能**修飾介面方法 因為介面是特殊的抽象類 不能新建例項 例項鎖應歸實現其的類所有 1. synchronized**不能**修飾構造方法(但可在內部使用synchronized程式碼塊來同步 ### 類的成員方法 修飾一個普通方法時,作用域是當前呼叫物件,即只要還沒出方法的作用域,其他試圖獲取該物件的鎖執行緒都將被阻塞。 這裡容易誤解的就是,只是嘗試獲取該物件鎖的執行緒會被阻塞,並不影響其他執行緒不獲取鎖瞎操作,所以要在涉及同步量操作的所有地方採用同步方法(如加鎖),否則引起執行緒安全問題幾乎是必然的。 ### 類的靜態方法 因為類的靜態方法屬於類,而不屬於類的某個特定例項,所以對類的靜態方法修飾直接作用於類本身,相當於synchronized(ClassA.class),即直接鎖定整個類。這裡有不少別人的筆記寫著,直接作用於類的所有物件,我覺得存在歧義,因為正常情況下,除非採用工廠模式之類的方法,不然很難獲取到所有物件的引用,並且這種表述也是不符合直覺的。 ## 同步程式碼塊 由於同步是一個高開銷操作,上面講的同步方法其實是同步程式碼塊的一個語法糖,平時應儘量使用synchronized同步關鍵程式碼,而不是對整個方法同步,要儘可能減少同步的內容。 對成員方法修飾 -> synchronized(this) 對靜態方法修飾 -> synchronized(ClassA.class) ## 總結 自己全部測試了一遍,重新驗證了猜想,目測沒有什麼不符合直覺的地方,另外,對單獨訊號量,如byte[]之類的加鎖操作,如果不釋放鎖,其他執行緒會全部阻塞在獲取鎖的過程中,這裡不單列出來。 本文前言中提到的問題,答案即為新建這個執行緒的例項本身,而不是這個被新建的執行緒類。 這裡看到結果就容易理解了,每個物件都自己與一個鎖相關聯,類靜態本身也與一個鎖關聯,任何嘗試獲取鎖的方法才可能會引起阻塞。 | **修飾物件/其他執行緒** | **同例項** | | | **其他例項** | | | **類** | | | | :---: | :---: | :---: | :---: | --- | --- | --- | --- | --- | --- | | **阻塞/不阻塞** | **成員變數** | **非同步方法** | **同步方法** | **成員變數** | **非同步方法** | **同步方法** | **靜態變數** | **靜態非同步方法** | **靜態同步方法** | | this | 不阻塞 | 不阻塞 | 阻塞 | 不阻塞 | | | 不阻塞 | | 不阻塞 | | 類的成員方法 | 不阻塞 | 不阻塞 | 阻塞 | | 不阻塞 | | 類.class | - | | | | 阻塞 | | 類的靜態方法 | | 阻塞 | ## 其他同步方法 這裡就不多介紹了,下面遇到了再詳細寫。 1. 使用volatile修飾域 每次使用此域都需重新計算 1. 使用ReentrantLock可重入鎖 需要注意及時手動釋放 通常在finally裡釋放 1. 使用ThreadLocal 這裡反對本文參考資料中的一個介紹 嚴格來說這不叫同步 只是各個使用到相同類的執行緒 獨立的建立一份自己的副本 **由於這個副本僅當前執行緒可達 也就沒有了其他執行緒的競爭** 相當於執行緒內部的全域性變數 應用場景主要有兩種 一是單個執行緒中多個類的例項共享另一個例項的時候 如資料庫連線、RequestContextHolder、Web Session、日誌的MDC、SimpleDateFormat(執行緒不安全的工具類)等 二是避免超長引數傳遞鏈 避免在方法中來回傳遞引數 1. 使用LinkedBlockingQueue阻塞佇列 利用佇列FIFO(先進先出)的特性實現生產者-消費者模型 1. 使用Atomic原子變數 利用原子操作本身的特性實現多執行緒同步 ## 參考資料 [java-synchronized-method-lock-on-object-or-method - stackoverflow](https://stackoverflow.com/questions/3047564/java-synchronized-method-lock-on-object-or-method) [what-is-the-reason-why-synchronized-is-not-allowed-in-java-8-interface-methods - stackoverflow](https://stackoverflow.com/questions/23453568/what-is-the-reason-why-synchronized-is-not-allowed-in-java-8-interface-methods) [Java執行緒同步的7種方式 - cnblogs](https://www.cnblogs.com/xhjt/p/3897440.html) [關於Java的構造方法在類初始化和類例項化中的實質 - CSDN](https://blog.csdn.net/justloveyou_/article/details/72466105) [Java中Synchronized的用法 - CSDN](https://blog.csdn.net/luoweifu/article/details/46613015) [Java多執行緒安全之建構函式 - CSDN](https://blog.csdn.net/u010963948/article/details/77651614) [正確理解Thread Local的原理與適用場景 - 個人部落格](http://www.jasongj.com/java/threadlocal/) [理解Java中的ThreadLocal - 個人部落格](https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/) [Java中的四種引用型別(強、軟、弱、虛) - 簡書](https://www.jianshu.com/p/ca6cbc