您好,登錄后才能下訂單哦!
這篇文章主要講解了“怎么使用synchronized ”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“怎么使用synchronized ”吧!
說起java的鎖呀,我們先想到的肯定是synchronized[?s?? kr? na? zd]了 ,這個單詞很拗口,會讀這個單詞在以后的面試中很加分(我面試過一些人 不會讀 ,他們說的是syn開頭那個單詞),不會讀略顯不專業,不過問題不大,會用,懂原理才是最重要的。
內容會由簡入難,有時候可以放棄一部分難的東西。 標記一下 回頭再看 可能更加明朗
廢話不多說,先寫hello world !!
例子: 小強 和 小明 同居了,但是只有一個廁所,他們每天必做的事情就是搶坑位,那么用代碼實現要怎么寫呢 ?
/** * @author 木子的晝夜 * <p> * 人 實體類 */ public class Person { // 名字 private String name; // 上廁所 public void gotoWc() { Wc.useWc(this); } public String getName() { return name; } public void setName(String name) { this.name = name; } } /** * @author 木子的晝夜 * * 廁所 實體類 */ public class Wc { /** * 使用廁所方法 * @param p 使用廁所的人 */ public static void useWc(Person p){ try{ System.out.println(p.getName()+" 正在使用廁所!!"); TimeUnit.SECONDS.sleep(10); System.out.println(p.getName()+" 用完了!!"); } catch (Exception e) { // 廁所萬一壞了 也得結束使用 System.out.println(p.getName()+" 用完了!!"); } } } /** * @author 木子的晝夜 * 這個測試 是小強與小明商量好了 說小強你先來 小強完事兒了 小明再來 * 這個不會發生沖突 因為是商量好的 順序執行 * 大家都知道 順序是不會出什么問題的 */ public class SyncTest { public static void main(String[] args) { // 小強對象 Person xiaoqiang = new Person(); xiaoqiang.setName("小強"); // 小明對象 Person xiaoming = new Person(); xiaoming.setName("小明"); // 上廁所 xiaoqiang.gotoWc(); xiaoming.gotoWc(); } }
上廁所過程:
如果倆人沒商量,自己去自己的呢 ?
/** * @author 木子的晝夜 */ public class SyncTest02 { public static void main(String[] args) { // 小強對象 Person xiaoqiang = new Person(); xiaoqiang.setName("小強"); // 小明對象 Person xiaoming = new Person(); xiaoming.setName("小明"); // 開啟兩個線程 誰也不理誰 自己干自己的 new Thread(()->xiaoqiang.gotoWc()).start(); new Thread(()->xiaoming.gotoWc()).start(); } }
上廁所過程:
上圖很明顯可以看出來,小強沒上完呢,小明就去上了,要是小的還湊活,大的怎么辦? 畫面自己想~~
這個時候大家可能想到了,廁所門上沒鎖嗎? 誰先進去鎖住不就行了嗎?
答對了!
/** * @author 木子的晝夜 * * 改造后 廁所 實體類 */ public class Wc { /** * 使用廁所方法 * synchronized: 誰先進廁所 馬上上鎖 !! * @param p 使用廁所的人 */ public static synchronized void useWc(Person p){ try{ System.out.println(p.getName()+" 正在使用廁所!!"); TimeUnit.SECONDS.sleep(10); System.out.println(p.getName()+" 用完了!!"); } catch (Exception e) { // 廁所萬一壞了 也得結束使用 System.out.println(p.getName()+" 用完了!!"); } } } /** * @author 木子的晝夜 */ public class SyncTest02 { public static void main(String[] args) { // 小強對象 Person xiaoqiang = new Person(); xiaoqiang.setName("小強"); // 小明對象 Person xiaoming = new Person(); xiaoming.setName("小明"); // 開啟兩個線程 誰也不理誰 自己干自己的 但是這次廁所有鎖, // 誰先進去 就把鎖鎖住 new Thread(()->xiaoqiang.gotoWc()).start(); new Thread(()->xiaoming.gotoWc()).start(); } }
上廁所過程:
可以看到,小強先上完,小明再上的,這樣就不會出什么問題了。
有人可能會問了,只見上鎖,沒有解鎖,小明怎么進去的?
這就是synchronized的一個特性了,它會自動釋放鎖, synchronized包裹的代碼執行完之后,鎖就自動釋放了。
所以避免了忘記釋放鎖,帶來的尷尬~
以廁所為例,自己想去吧。 提出:”共享資源“ 這個詞就對了
1) 對象鎖 顧名思義 就是鎖一個對象
/** * @author 木子的晝夜 * 對象鎖 */ public class SyncObject { /** * 累加值 (共享資源) */ int count = 0; /** * 鎖對象 */ private Object lock = new Object(); public static void main(String[] args) { SyncObject so = new SyncObject(); // 線程1 new Thread(()->{ try { for (;;){ TimeUnit.SECONDS.sleep(2); so.increaseCount(); } } catch (Exception e) { System.err.println("錯誤"); } },"線程1").start(); // 線程2 new Thread(()->{ try { for (;;){ TimeUnit.SECONDS.sleep(2); so.increaseCount(); } } catch (Exception e) { System.err.println("錯誤"); } },"線程2").start(); } /** * count累加 */ public void increaseCount(){ // 加鎖 synchronized (lock){ count = count+1; System.out.println(Thread.currentThread().getName()+" count="+count); } } }
例子中:兩個線程增加count ,鎖的是lock這個對象 ,這就叫對象鎖 。
有時候會看見synchronized(this) 這是什么鎖 ? this嘛 就是指當前對象,也是對象鎖,
synchronized(this) 相當于 在方法上加synchronized,下邊這兩個方法都是鎖的當前對象
/** * count累加 */ public void increaseCount(){ // 加鎖 synchronized (this){ count = count+1; System.out.println(Thread.currentThread().getName()+" count="+count); } } /** * count累加 加鎖 */ public synchronized void increaseCount02(){ count = count+1; System.out.println(Thread.currentThread().getName()+" count="+count); }
2) 類鎖 顧名思義 就是給一個類加鎖
每個類load到內存之后呢,會生成一個Class類型的對象 鎖的就是他
其實也是鎖一個對象 只是這個對象比較特殊,它代表類
/** * @author 木子的晝夜 * 對象鎖 */ public class SyncObject03 { /** * 累加值 (共享資源) */ static int count = 0; /** * 鎖對象 */ private Object lock = new Object(); public static void main(String[] args) { SyncObject03 so = new SyncObject03(); // 線程1 new Thread(()->{ for (;;){ SyncObject03.increaseCount(); } },"線程1").start(); // 線程2 new Thread(()->{ for (;;){ SyncObject03.increaseCount(); } },"線程2").start(); } /** * count累加 */ public synchronized static void increaseCount(){ // 加鎖 try { count = count+1; System.out.println(Thread.currentThread().getName()+" count="+count); TimeUnit.SECONDS.sleep(1); } catch (Exception e){ System.err.println("錯誤~"); } } /** * count累加 */ public void increaseCount02(){ synchronized(SyncObject03.class){ // 加鎖 try { count = count+1; System.out.println(Thread.currentThread().getName()+" count="+count); TimeUnit.SECONDS.sleep(1); } catch (Exception e){ System.err.println("錯誤~"); } } } }
以上兩種方式 都是類鎖。
這是一個用腳指頭就能想到的答案,但是好多面試官問。 問了之后呢 你就蒙了~~ 難道不能?
答案是:能!
為什么能呢?因為愛所以愛~~ 錯了,重來 .. 因為能所以能~~
小明在吃飯,給碗上個鎖,別人不能用, 那小明能同時看他的偶像鄧紫棋唱歌嗎 ?
誰要是說不可以,以后吃飯不讓他玩手機、pad、電腦 。 就讓他吃吃吃 (那是豬)
/** * @author 木子的晝夜 * 可能出現的面試題 */ public class SyncObject04 { private Object lock = new Object(); public static void main(String[] args) throws InterruptedException { SyncObject04 so = new SyncObject04(); // 線程1 new Thread(()->{ so.increaseCount(); },"線程1").start(); Thread.sleep(2000); // 線程2 new Thread(()->{ so.lookMv(); },"線程2").start(); } /** * 吃飯 */ public void increaseCount(){ // 加鎖 synchronized (this){ try{ System.out.println("吃飯 "); // 也可以在吃飯這里 跟妹子聊天 chatWithGirl(); TimeUnit.SECONDS.sleep(10); } catch (Exception e) { System.err.println("飯掉地上了~"); } System.out.println("吃完飯 "); } } /** * 看演唱會視頻 */ public void lookMv(){ System.out.println("看演唱會視頻"); } /** * 看跟美女聊天 */ public void chatWithGirl(){ System.out.println("跟美女聊天"); } }
能一句話總結嗎?
咳咳: 同一線程可以調用加了同一把鎖的兩個方法 不會阻塞。
例子:同一個人可以用同一雙筷子(筷子加鎖),吃不同的菜~~
吃魚的時候獲取了鎖,在吃魚方法里調用吃沙拉方法,是可以調用成功了 因為兩個方法用的同一把鎖
/** * @author 木子的晝夜 */ public class TestC { /** * 一雙筷子 只能一個人同一時間使用(一個線程) */ Object chopsticks = new Object(); public static void main(String[] args) { TestC c = new TestC(); new Thread(()->{ c.eatFish(); }).start(); } /** * 吃 */ public void eatFish( ) { synchronized (chopsticks) { try { System.out.println("吃 魚"); Thread.sleep(2000); eatSalad(); } catch (Exception e){ } } } /** * 吃沙拉 */ public void eatSalad() { synchronized (chopsticks) { try { System.out.println("吃 沙拉"); Thread.sleep(2000); eatFish(); } catch (Exception e){ } } } }
jdk1.6之前 synchronized 是 重量級鎖 什么是重量級鎖? 就是每次鎖都會去找操作系統申請鎖。
jdk1.6及以后改進為鎖升級
簡單思路是:
synchronized(object)
線程A 第一個訪問
偏向鎖 只在object的markword 中記錄線程A的線程ID
如果線程A 又進來訪問 一看markword的線程號是自己 那就直接用
這時候線程B 來了 ,線程B 一看我擦? 有人占用了鎖!
線程B 會循環死等 ,類似在廁所門口,敲敲門問問線程A 你好了嗎?敲敲門問問線程A 你好了嗎?敲敲門問問線程A 你好了嗎?敲敲門問問線程A 你好了嗎?
線程B的這一操作,用術語將叫: 自旋鎖
線程B 問10次之后,得不到鎖,就會升級為重量級鎖 (去操作系統申請資源)
無鎖->偏向鎖->自旋鎖->重量級鎖
鎖 一般 。。 只升級 不降級
什么是ABA問題,假如你有媳婦兒,我說假如~ 以偷零花錢為例
https://www.processon.com/view/link/603c96ca07912913b4f2c55f
這是一個故事: 小強偷零花錢請小明吃飯的故事
ABA 就是:
小強媳婦兒 出門看的錢是10萬 (A)
小強偷拿1萬 剩余9萬(B)
小強找小月借了1萬 放回去 總共10萬(A)
小強媳婦兒回來 一看是10萬,很滿意。 但是 她不知道 這是偷梁換柱啊
后來小強媳婦兒看了我的博客 , 發現了秘密 ,她應該怎么解決呢 ?
關鍵字:version 版本號
她上班之前,在家里的存款上用筆寫了一個版本,小強如果再偷錢,再還回來,這個版本就變了(+1)
這樣就解決了ABA問題
重度競爭: 耗時過長 自選過多 wait等
新建對象可能直接是匿名偏向 ( 如果默認開啟了偏向鎖) ,因為沒有偏向任何一個線程,所以是匿名偏向
JVM默認不開啟 延遲4秒后才會開啟 偏向鎖
1. 查看工具 : JOL (Java Object Layout )
直接maven引入就可以使用了
<dependencies> <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency> </dependencies>
/** * @author 木子的晝夜 */ public class Person { long money; public long getMoney() { return money; } public void setMoney(long money) { this.money = money; } } /** * @author 木子的晝夜 */ public class JolTest { public static void main(String[] args) { Person p = new Person(); System.out.println(ClassLayout.parseInstance(p).toPrintable()); } } // 輸出結果 Person object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 4 4 (object header) 00 00 00 00 8 4 (object header) 43 c1 00 f8 12 4 (alignment/padding gap) 16 8 long Person.money 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
咦? 不是要寫synchronized 嗎 ?
2. markword
我給對象p 加鎖,然后輸出 layout 可以發現 markword 改變了
所以呢~~ 鎖信息 是記錄再markword中的
/** * @author 木子的晝夜 */ public class JolTest { public static void main(String[] args) { Person p = new Person(); System.out.println(ClassLayout.parseInstance(p).toPrintable()); synchronized (p) { System.out.println(ClassLayout.parseInstance(p).toPrintable()); } } }
markword 這么厲害嗎? 不 ! 它還能更厲害 。 我們看一下 它里邊都記錄了一些什么信息
先暫時看最后3bit 其他 不是很了解 這個時候可以對比上邊layout的輸出 看一下
剛開始是01 加鎖之后變成了00 (沒有開啟偏向鎖 直接到輕量級鎖)
先看最后2bit:
00:輕量級鎖 自旋鎖
自旋鎖,耗CPU資源 是在用戶態操作 不關聯內核態
兩個線程 爭著把自己的Lock Record 放到markword中
誰先放進去,誰先獲得鎖,另一個人接著cas 去放
Lock Record 指向的是什么呢 是無鎖狀態的markword
這就解釋了 為什么hashcode不丟失的問題 因為有備份記錄
這里鎖重入: 上邊提到了,鎖重入 ,鎖每進一次,都會加一個LR 從第二個LR開始 指向的就是一個null
等鎖退出 也就是monitorexit(鎖代碼塊執行完 或 拋異常)的時候LR -1 ,LR -1 ,LR -1 一直減 ,退一次減一次
10:重量級鎖
向OS 申請鎖,進了內核態 , c++ 新建了一個object monitor對象 markword中放的就是這個 指針 (java中就是個地址或者是ID )
重量級鎖,都在一個隊列里等著,比較不消耗CPU資源
可重入鎖: 重量級是記錄再object moniter 的某個屬性上
什么時候自選上升為重量級鎖:
1. 自選次數超過10次 或者 自選的線程數超過CPU的一半 2. jdk1.6之前 -XX:PreBlockSpin可以調整 自選超過多少次升級 3. jdk1.6之后加入了自適應 Adapative Self Sping JVM自己個兒控制
11:GC回收標記
01: 再看倒數第三位
0:無鎖
1:偏向鎖 放線程ID , c++實現是用的指針
3. synchronized 編譯成字節碼 會有兩個單詞 monitorenter monitorexit
什么時候monitorexit呢 , 代碼執行完 ,或者是異常發生 這就是synchronized 自動釋放鎖的原理
(1) 自旋是消耗CPU資源的,如果鎖的時間長,或者自旋線程多,CPU會被大量消耗
(2) 重量級鎖有等待隊列,所有拿不到鎖的進入等待隊列,不需要消耗CPU資源
(1)不一定 當你知道肯定存在多線程競爭的時候,偏向鎖會涉及鎖撤銷,這時候自旋鎖會比較好一點
(2) JVM啟動過程就會很很多線程競爭,所以默認不開啟偏向鎖,過一段時間才會開啟
(3) -XX:BiasedLockingStartupDelay = 0 默認是 4 秒
感謝各位的閱讀,以上就是“怎么使用synchronized ”的內容了,經過本文的學習后,相信大家對怎么使用synchronized 這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。