亚洲精品中文免费|亚洲日韩中文字幕制服|久久精品亚洲免费|一本之道久久免费

      
      

            <dl id="hur0q"><div id="hur0q"></div></dl>

                深入Synchronized各種使用方法

                深入學習Synchronized各種使用方法

                在Java當中synchronized通常是用來標記一個方法或者代碼塊。在Java當中被synchronized標記的代碼或者方法在同一個時刻只能夠有一個線程執(zhí)行被synchronized修飾的方法或者代碼塊。因此被synchronized修飾的方法或者代碼塊不會出現(xiàn) 數(shù)據(jù)競爭 的情況,也就是說被synchronized修飾的代碼塊是并發(fā)安全的。

                Synchronized關(guān)鍵字

                synchronized關(guān)鍵字通常使用在下面四個地方:

                • synchronized修飾實例方法。
                • synchronized修飾靜態(tài)方法。
                • synchronized修飾實例方法的代碼塊。
                • synchronized修飾靜態(tài)方法的代碼塊。

                在實際情況當中我們需要仔細分析我們的需求選擇合適的使用synchronized方法,在保證程序正確的情況下提升程序執(zhí)行的效率。

                Synchronized修飾實例方法

                下面是一個用Synchronized修飾實例方法的代碼示例:

                public class SyncDemo { private int count; public synchronized void add() { count++; } public static void main(String[] args) throws InterruptedException { SyncDemo syncDemo = new SyncDemo(); Thread t1 = new Thread(() -> { for (int i = 0; i { for (int i = 0; i < 10000; i++) { syncDemo.add(); } }); t1.start(); t2.start(); t1.join(); // 阻塞住線程等待線程 t1 執(zhí)行完成 t2.join(); // 阻塞住線程等待線程 t2 執(zhí)行完成 System.out.println(syncDemo.count);// 輸出結(jié)果為 20000 }}

                在上面的代碼當中的 add 方法只有一個簡單的 count++ 操作,因為這個方法是使用 synchronized 修飾的因此每一個時刻只能有一個線程執(zhí)行 add 方法,因此上面打印的結(jié)果是20000。如果 add 方法沒有使用 synchronized 修飾的話,那么線程t1和線程t2就可以同時執(zhí)行 add 方法,這可能會導致最終 count 的結(jié)果小于20000,因為 count++ 操作不具備原子性。

                上面的分析還是比較明確的,但是我們還需要知道的是 synchronized 修飾的 add 方法一個時刻只能有一個線程執(zhí)行的意思是對于一個 SyncDemo 類的對象來說一個時刻只能有一個線程進入。比如現(xiàn)在有兩個 SyncDemo 的對象 s1 和 s2 ,一個時刻只能有一個線程進行 s1 的 add 方法,一個時刻只能有一個線程進入 s2 的 add 方法,但是同一個時刻可以有兩個不同的線程執(zhí)行 s1 和 s2 的 add 方法,也就說 s1 的 add 方法和 s2 的 add 是沒有關(guān)系的,一個線程進入 s1 的 add 方法并不會阻止另外的線程進入 s2 的 add 方法,也就是說 synchronized 在修飾一個非靜態(tài)方法的時候“鎖”住的只是一個實例對象,并不會“鎖”住其它的對象。其實這也很容易理解,一個實例對象是一個獨立的個體別的對象不會影響他,他也不會影響別的對象。

                Synchronized修飾靜態(tài)方法

                Synchronized修飾靜態(tài)方法:

                public class SyncDemo { private static int count; public static synchronized void add() { count++; // 注意 count 也要用 static 修飾 否則編譯通過不了 } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i { for (int i = 0; i < 10000; i++) { SyncDemo.add(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(SyncDemo.count); // 輸出結(jié)果為 20000 }}

                上面的代碼最終輸出的結(jié)果也是20000,但是與前一個程序不同的是。這里的 add 方法用 static 修飾的,在這種情況下真正的只能有一個線程進入到 add 代碼塊,因為用 static 修飾的話是所有對象公共的,因此和前面的那種情況不同,不存在兩個不同的線程同一時刻執(zhí)行 add 方法。

                你仔細想想如果能夠讓兩個不同的線程執(zhí)行 add 代碼塊,那么 count++ 的執(zhí)行就不是原子的了。那為什么沒有用 static 修飾的代碼為什么可以呢?因為當沒有用 static 修飾時,每一個對象的 count 都是不同的,內(nèi)存地址不一樣,因此在這種情況下 count++ 這個操作仍然是原子的!

                Sychronized修飾多個方法

                synchronized修飾多個方法示例:

                public class AddMinus { public static int ans; public static synchronized void add() { ans++; } public static synchronized void minus() { ans–; } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i { for (int i = 0; i < 10000; i++) { AddMinus.minus(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(AddMinus.ans); // 輸出結(jié)果為 0 }}

                在上面的代碼當中我們用 synchronized 修飾了兩個方法, add 和 minus 。這意味著在同一個時刻這兩個函數(shù)只能夠有一個被一個線程執(zhí)行,也正是因為 add 和 minus 函數(shù)在同一個時刻只能有一個函數(shù)被一個線程執(zhí)行,這才會導致 ans 最終輸出的結(jié)果等于0。

                對于一個實例對象來說:

                public class AddMinus { public int ans; public synchronized void add() { ans++; } public synchronized void minus() { ans–; } public static void main(String[] args) throws InterruptedException { AddMinus addMinus = new AddMinus(); Thread t1 = new Thread(() -> { for (int i = 0; i { for (int i = 0; i < 10000; i++) { addMinus.minus(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(addMinus.ans); }}

                上面的代碼沒有使用 static 關(guān)鍵字,因此我們需要 new 出一個實例對象才能夠調(diào)用 add 和 minus 方法,但是同樣對于 AddMinus 的實例對象來說同一個時刻只能有一個線程在執(zhí)行 add 或者 minus 方法,因此上面代碼的輸出同樣是0。

                Synchronized修飾實例方法代碼塊

                Synchronized修飾實例方法代碼塊

                public class CodeBlock { private int count; public void add() { System.out.println(“進入了 add 方法”); synchronized (this) { count++; } } public void minus() { System.out.println(“進入了 minus 方法”); synchronized (this) { count–; } } public static void main(String[] args) throws InterruptedException { CodeBlock codeBlock = new CodeBlock(); Thread t1 = new Thread(() -> { for (int i = 0; i { for (int i = 0; i < 10000; i++) { codeBlock.minus(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(codeBlock.count); // 輸出結(jié)果為 0 }}

                有時候我們并不需要用 synchronized 去修飾代碼塊,因為這樣并發(fā)度就比較低了,一個方法一個時刻只能有一個線程在執(zhí)行。因此我們可以選擇用 synchronized 去修飾代碼塊,只讓某個代碼塊一個時刻只能有一個線程執(zhí)行,除了這個代碼塊之外的代碼還是可以并行的。

                比如上面的代碼當中 add 和 minus 方法沒有使用 synchronized 進行修飾,因此一個時刻可以有多個線程執(zhí)行這個兩個方法。在上面的 synchronized 代碼塊當中我們使用了 this 對象作為鎖對象,只有拿到這個鎖對象的線程才能夠進入代碼塊執(zhí)行,而在同一個時刻只能有一個線程能夠獲得鎖對象。也就是說 add 函數(shù)和 minus 函數(shù)用 synchronized 修飾的兩個代碼塊同一個時刻只能有一個代碼塊的代碼能夠被一個線程執(zhí)行,因此上面的結(jié)果同樣是0。

                這里說的鎖對象是 this 也就 CodeBlock 類的一個實例對象,因為它鎖住的是一個實例對象,因此當實例對象不一樣的時候他們之間是沒有關(guān)系的,也就是說不同實例用 synchronized 修飾的代碼塊是沒有關(guān)系的,他們之間是可以并發(fā)的。

                Synchronized修飾靜態(tài)代碼塊

                public class CodeBlock { private static int count; public static void add() { System.out.println(“進入了 add 方法”); synchronized (CodeBlock.class) { count++; } } public static void minus() { System.out.println(“進入了 minus 方法”); synchronized (CodeBlock.class) { count–; } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i { for (int i = 0; i < 10000; i++) { CodeBlock.minus(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(CodeBlock.count); }}

                上面的代碼是使用 synchronized 修飾靜態(tài)代碼塊,上面代碼的鎖對象是 CodeBlock.class ,這個時候他不再是鎖住一個對象了,而是一個類了,這個時候的并發(fā)度就變小了,上一份代碼當鎖對象是 CodeBlock 的實例對象時并發(fā)度更大一些,因為當鎖對象是實例對象的時候,只有實例對象內(nèi)部是不能夠并發(fā)的,實例之間是可以并發(fā)的。但是當鎖對象是 CodeBlock.class 的時候,實例對象之間時不能夠并發(fā)的,因為這個時候的鎖對象是一個類。

                應該用什么對象作為鎖對象

                在前面的代碼當中我們分別使用了實例對象和類的class對象作為鎖對象,事實上你可以使用任何對象作為鎖對象,但是不推薦使用字符串和基本類型的包裝類作為鎖對象,這是因為字符串對象和基本類型的包裝對象會有緩存的問題。字符串有字符串常量池,整數(shù)有小整數(shù)池。因此在使用這些對象的時候他們可能最終都指向同一個對象,因為指向的都是同一個對象,線程獲得鎖對象的難度就會增加,程序的并發(fā)度就會降低。

                比如在下面的示例代碼當中就是由于鎖對象是同一個對象而導致并發(fā)度下降:

                import java.util.concurrent.TimeUnit;public class Test { public void testFunction() throws InterruptedException { synchronized (“HELLO WORLD”) { System.out.println(Thread.currentThread().getName() + “I am in synchronized code block”); TimeUnit.SECONDS.sleep(5); } } public static void main(String[] args) { Test t1 = new Test(); Test t2 = new Test(); Thread thread1 = new Thread(() -> { try { t1.testFunction(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread thread2 = new Thread(() -> { try { t2.testFunction(); } catch (InterruptedException e) { e.printStackTrace(); } }); thread1.start(); thread2.start(); }}

                在上面的代碼當中我們使用兩個不同的線程執(zhí)行兩個不同的對象內(nèi)部的 testFunction 函數(shù),按道理來說這兩個線程是可以同時執(zhí)行的,因為執(zhí)行的是兩個不同的實例對象的同步代碼塊。但是上面代碼的執(zhí)行首先一個線程會進入同步代碼塊然后打印輸出,等待5秒之后,這個線程退出同步代碼塊另外一個線程才會再進入同步代碼塊,這就說明了兩個線程不是同時執(zhí)行的,其中一個線程需要等待另外一個線程執(zhí)行完成才執(zhí)行。這正是因為兩個 Test 對象當中使用的 “HELLO WORLD” 字符串在內(nèi)存當中是同一個對象,是存儲在字符串常量池中的對象,這才導致了鎖對象的競爭。

                下面的代碼執(zhí)行的結(jié)果也是一樣的,一個線程需要等待另外一個線程執(zhí)行完成才能夠繼續(xù)執(zhí)行,這是因為在Java當中如果整數(shù)數(shù)據(jù)在 [-128, 127] 之間的話使用的是小整數(shù)池當中的對象,使用的也是同一個對象,這樣可以減少頻繁的內(nèi)存申請和回收,對內(nèi)存更加友好。

                import java.util.concurrent.TimeUnit;public class Test { public void testFunction() throws InterruptedException { synchronized (Integer.valueOf(1)) { System.out.println(Thread.currentThread().getName() + “I am in synchronized code block”); TimeUnit.SECONDS.sleep(5); } } public static void main(String[] args) { Test t1 = new Test(); Test t2 = new Test(); Thread thread1 = new Thread(() -> { try { t1.testFunction(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread thread2 = new Thread(() -> { try { t2.testFunction(); } catch (InterruptedException e) { e.printStackTrace(); } }); thread1.start(); thread2.start(); }}

                Synchronized與可見性和重排序

                可見性

                • 當一個線程進入到 synchronized 同步代碼塊的時候,將會刷新所有對該線程的可見的變量,也就是說如果其他線程修改了某個變量,而且線程需要在 Synchronized 代碼塊當中使用,那就會重新刷新這個變量到內(nèi)存當中,保證這個變量對于執(zhí)行同步代碼塊的線程是可見的。
                • 當一個線程從同步代碼塊退出的時候,也會將線程的工作內(nèi)存同步到內(nèi)存當中,保證在同步代碼塊當中修改的變量對其他線程可見。

                重排序

                Java編譯器和JVM當發(fā)現(xiàn)能夠讓程序執(zhí)行的更快的時候是可能對程序的指令進行重排序處理的,也就是通過調(diào)換程序指令執(zhí)行的順序讓程序執(zhí)行的更快。

                但是重排序很可能讓并發(fā)程序產(chǎn)生問題,比如說當一個在 synchronized 代碼塊當中的寫操作被重排序到 synchronized 同步代碼塊外部了這顯然是有問題的。

                在JVM的實現(xiàn)當中是不允許 synchronized 代碼塊內(nèi)部的指令和他前面和后面的指令進行重排序的,但是在 synchronized 內(nèi)部的指令是可能與 synchronized 內(nèi)部的指令進行重排序的,比較著名的就是 DCL單例模式 ,他就是在 synchronized 代碼塊當中存在重排序的,如果你對 DCL單例模式 還不是很熟悉,你可以閱讀 這篇文章 的 DCL單例 模式部分。

                總結(jié)

                在本篇文章當中主要介紹了各種 synchronized 的使用方法,總結(jié)如下:

                volatile

                鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
                用戶投稿
                上一篇 2022年8月29日 18:16
                下一篇 2022年8月29日 18:17

                相關(guān)推薦

                • 分享4條發(fā)微商朋友圈的方法(微商朋友圈應該怎么發(fā))

                  對于微商朋友來說,朋友圈的重要性不言而喻了。 那么微商的朋友圈到底該怎么發(fā)呢? 為什么同樣是經(jīng)營一個朋友圈,有的微商看起來逼格滿滿,實際效果也不錯;而有的卻動都不動就被屏蔽甚至拉黑…

                  2022年11月27日
                • 筆記本最好配置(目前筆記本最好的配置)

                  本文主要講的是筆記本最好配置,以及和目前筆記本最好的配置相關(guān)的知識,如果覺得本文對您有所幫助,不要忘了將本文分享給朋友。 筆記本電腦什么配置好? 01 CPU:這個主要取決于頻率和…

                  2022年11月26日
                • 存儲過程語法(sql server存儲過程語法)

                  今天小編給各位分享存儲過程語法的知識,其中也會對sql server存儲過程語法進行解釋,如果能碰巧解決你現(xiàn)在面臨的問題,別忘了關(guān)注本站,現(xiàn)在開始吧! oracle存儲過程基本語法…

                  2022年11月26日
                • 《寶可夢朱紫》夢特性怎么獲得?隱藏特性獲取方法推薦

                  寶可夢朱紫里有很多寶可夢都是擁有夢特性會變強的寶可夢,很多玩家不知道夢特性怎么獲得,下面就給大家?guī)韺毧蓧糁熳想[藏特性獲取方法推薦,感興趣的小伙伴一起來看看吧,希望能幫助到大家。 …

                  2022年11月25日
                • 《寶可夢朱紫》奇魯莉安怎么進化?奇魯莉安進化方法分享

                  寶可夢朱紫中的奇魯莉安要怎么進化呢?很多玩家都不知道,下面就給大家?guī)韺毧蓧糁熳掀骠斃虬策M化方法分享,感興趣的小伙伴一起來看看吧,希望能幫助到大家。 奇魯莉安進化方法分享 奇魯莉安…

                  2022年11月25日
                • 非匿名指令新手十連怎么選?非匿名指令新手無限十連選擇推薦

                  非匿名指令新手十連怎么選?進入游戲之后大家能得到一個新手無限十連,可以幫大家抽到滿意的角色,新手十連的選擇小編在下面也會有分享,不知道如何選擇的可以看看小編提供的攻略,了解新手十連…

                  2022年11月25日
                • cpu性能天梯圖2022 AMD CPU天梯圖最新排行榜出爐

                  用戶在DIY自己的主機時選擇CPU是非常關(guān)鍵的,CPU可以說是電腦的大腦,大家也都想追求好一點的CPU來使用,但型號太多了,大部分的用戶都不知道目前哪一款CPU比較好用,快來看看詳…

                  2022年11月24日
                • 今天出入濟南最新通知(出入濟南政策最新消息今天)

                  近日濟南疫情感染人數(shù)也在不斷增加,劃分的高風險區(qū)也是越來越多了。據(jù)最新統(tǒng)計,截止2022年11月24日11時,濟南共有低風險地區(qū)12處,高風險地區(qū)338處。很多朋友都擔心現(xiàn)在濟南無…

                  2022年11月24日
                • 《寶可夢朱紫》暴飛龍怎么抓?暴飛龍獲得方法

                  寶可夢朱紫暴飛龍位置在哪?在游戲中,很多玩家還不清楚暴飛龍具體要怎么樣獲得,其實獲得方法很簡單,暴飛龍直接是沒得抓的,需要玩家從寶貝龍進化得到,下面一起來看一下寶可夢朱紫暴飛龍獲得…

                  2022年11月23日
                • 《寶可夢朱紫》布土撥怎么進化?布土撥進化方法介紹

                  寶可夢朱紫中,不同的寶可夢有不同的進化方法,其中布土撥的進化方法是比較特殊的。很多玩家不知道寶可夢朱紫布土撥怎么進化,下面就帶來寶可夢朱紫布土撥進化方法介紹,一起來看看吧,希望能幫…

                  2022年11月23日

                聯(lián)系我們

                聯(lián)系郵箱:admin#wlmqw.com
                工作時間:周一至周五,10:30-18:30,節(jié)假日休息