在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    面了一個(gè)25歲的學(xué)妹,把synchronized關(guān)鍵字講的那叫一個(gè)透徹

    面了一個(gè)25歲的學(xué)妹,把synchronized關(guān)鍵字講的那叫一個(gè)透徹

    二哥:“三妹,以前都是我講你聽(tīng),今天我們換個(gè)形式,假裝我是面試官,你來(lái)講講 synchronized 關(guān)鍵字的應(yīng)用方式和內(nèi)存語(yǔ)義吧?!?/p>

    三妹(顏值在線,氣質(zhì)也在線):“我們這是要互換角色嗎?哈哈哈哈?!?/p>

    前言

    “好吧,來(lái)吧?!比靡稽c(diǎn)也不怯場(chǎng)。

    在 Java 中,關(guān)鍵字 synchronized 可以保證在同一個(gè)時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊(主要是對(duì)方法或者代碼塊中存在共享數(shù)據(jù)的操作),同時(shí)我們還應(yīng)該注意到 synchronized 另外一個(gè)重要的作用,synchronized 可保證一個(gè)線程的變化(主要是共享數(shù)據(jù)的變化)被其他線程所看到(保證可見(jiàn)性,完全可以替代 Volatile 功能)。

    synchronized 的三種應(yīng)用方式

    synchronized 關(guān)鍵字最主要有以下 3 種應(yīng)用方式,下面分別介紹:

    • 修飾實(shí)例方法,作用于當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖;
    • 修飾靜態(tài)方法,作用于當(dāng)前類對(duì)象加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類對(duì)象的鎖;
    • 修飾代碼塊,指定加鎖對(duì)象,對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼庫(kù)前要獲得給定對(duì)象的鎖。

    synchronized 作用于實(shí)例方法

    所謂的實(shí)例對(duì)象鎖就是用 synchronized 修飾實(shí)例對(duì)象中的實(shí)例方法,注意是實(shí)例方法不包括靜態(tài)方法,如下:

    public class AccountingSync implements Runnable { //共享資源(臨界資源) static int i = 0; // synchronized 修飾實(shí)例方法 public synchronized void increase() { i ++; } @Override public void run() { for(int j=0;j<1000000;j++){ increase(); } } public static void main(String args[]) throws InterruptedException { AccountingSync instance = new AccountingSync(); Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("static, i output:" + i); }}/** * 輸出結(jié)果: * static, i output:2000000 */

    如果在函數(shù) increase()前不加 synchronized,因?yàn)?i++不具備原子性,所以最終結(jié)果會(huì)小于 2000000,具體分析可以參考文章《Java 并發(fā)編程系列 2-volatile》。下面這點(diǎn)非常重要:

    一個(gè)對(duì)象只有一把鎖,當(dāng)一個(gè)線程獲取了該對(duì)象的鎖之后,其他線程無(wú)法獲取該對(duì)象的鎖,所以無(wú)法訪問(wèn)該對(duì)象的其他 synchronized 實(shí)例方法,但是其他線程還是可以訪問(wèn)該實(shí)例對(duì)象的其他非 synchronized 方法。

    但是一個(gè)線程 A 需要訪問(wèn)實(shí)例對(duì)象 obj1 的 synchronized 方法 f1(當(dāng)前對(duì)象鎖是 obj1),另一個(gè)線程 B 需要訪問(wèn)實(shí)例對(duì)象 obj2 的 synchronized 方法 f2(當(dāng)前對(duì)象鎖是 obj2),這樣是允許的:

    public class AccountingSyncBad implements Runnable { //共享資源(臨界資源) static int i = 0; // synchronized 修飾實(shí)例方法 public synchronized void increase() { i ++; } @Override public void run() { for(int j=0;j<1000000;j++){ increase(); } } public static void main(String args[]) throws InterruptedException { // new 兩個(gè)AccountingSync新實(shí)例 Thread t1 = new Thread(new AccountingSyncBad()); Thread t2 = new Thread(new AccountingSyncBad()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("static, i output:" + i); }}/** * 輸出結(jié)果: * static, i output:1224617 */

    上述代碼與前面不同的是我們同時(shí)創(chuàng)建了兩個(gè)新實(shí)例 AccountingSyncBad,然后啟動(dòng)兩個(gè)不同的線程對(duì)共享變量 i 進(jìn)行操作,但很遺憾操作結(jié)果是 1224617 而不是期望結(jié)果 2000000,因?yàn)樯鲜龃a犯了嚴(yán)重的錯(cuò)誤,雖然我們使用 synchronized 修飾了 increase 方法,但卻 new 了兩個(gè)不同的實(shí)例對(duì)象,這也就意味著存在著兩個(gè)不同的實(shí)例對(duì)象鎖,因此 t1 和 t2 都會(huì)進(jìn)入各自的對(duì)象鎖,也就是說(shuō) t1 和 t2 線程使用的是不同的鎖,因此線程安全是無(wú)法保證的。

    每個(gè)對(duì)象都有一個(gè)對(duì)象鎖,不同的對(duì)象,他們的鎖不會(huì)互相影響。

    解決這種困境的的方式是將 synchronized 作用于靜態(tài)的 increase 方法,這樣的話,對(duì)象鎖就當(dāng)前類對(duì)象,由于無(wú)論創(chuàng)建多少個(gè)實(shí)例對(duì)象,但對(duì)于的類對(duì)象擁有只有一個(gè),所有在這樣的情況下對(duì)象鎖就是唯一的。下面我們看看如何使用將 synchronized 作用于靜態(tài)的 increase 方法。

    synchronized 作用于靜態(tài)方法

    當(dāng) synchronized 作用于靜態(tài)方法時(shí),其鎖就是當(dāng)前類的 class 鎖,不屬于某個(gè)對(duì)象。

    當(dāng)前類 class 鎖被獲取,不影響對(duì)象鎖的獲取,兩者互不影響。

    由于靜態(tài)成員不專屬于任何一個(gè)實(shí)例對(duì)象,是類成員,因此通過(guò) class 對(duì)象鎖可以控制靜態(tài)成員的并發(fā)操作。需要注意的是如果一個(gè)線程 A 調(diào)用一個(gè)實(shí)例對(duì)象的非 static synchronized 方法,而線程 B 需要調(diào)用這個(gè)實(shí)例對(duì)象所屬類的靜態(tài) synchronized 方法,不會(huì)發(fā)生互斥現(xiàn)象,因?yàn)樵L問(wèn)靜態(tài) synchronized 方法占用的鎖是當(dāng)前類的 class 對(duì)象,而訪問(wèn)非靜態(tài) synchronized 方法占用的鎖是當(dāng)前實(shí)例對(duì)象鎖,看如下代碼:

    public class AccountingSyncClass implements Runnable { static int i = 0; /** * 作用于靜態(tài)方法,鎖是當(dāng)前class對(duì)象,也就是 * AccountingSyncClass類對(duì)應(yīng)的class對(duì)象 */ public static synchronized void increase() { i++; } // 非靜態(tài),訪問(wèn)時(shí)鎖不一樣不會(huì)發(fā)生互斥 public synchronized void increase4Obj() { i++; } @Override public void run() { for(int j=0;j<1000000;j++){ increase(); } } public static void main(String[] args) throws InterruptedException { //new新實(shí)例 Thread t1=new Thread(new AccountingSyncClass()); //new新實(shí)例 Thread t2=new Thread(new AccountingSyncClass()); //啟動(dòng)線程 t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); }}/** * 輸出結(jié)果: * 2000000 */

    由于 synchronized 關(guān)鍵字修飾的是靜態(tài) increase 方法,與修飾實(shí)例方法不同的是,其鎖對(duì)象是當(dāng)前類的 class 對(duì)象。注意代碼中的 increase4Obj 方法是實(shí)例方法,其對(duì)象鎖是當(dāng)前實(shí)例對(duì)象,如果別的線程調(diào)用該方法,將不會(huì)產(chǎn)生互斥現(xiàn)象,畢竟鎖對(duì)象不同,但我們應(yīng)該意識(shí)到這種情況下可能會(huì)發(fā)現(xiàn)線程安全問(wèn)題(操作了共享靜態(tài)變量 i)。

    synchronized 同步代碼塊

    在某些情況下,我們編寫(xiě)的方法體可能比較大,同時(shí)存在一些比較耗時(shí)的操作,而需要同步的代碼又只有一小部分,如果直接對(duì)整個(gè)方法進(jìn)行同步操作,可能會(huì)得不償失,此時(shí)我們可以使用同步代碼塊的方式對(duì)需要同步的代碼進(jìn)行包裹,這樣就無(wú)需對(duì)整個(gè)方法進(jìn)行同步操作了,同步代碼塊的使用示例如下:

    public class AccountingSync2 implements Runnable { static AccountingSync2 instance = new AccountingSync2(); // 餓漢單例模式 static int i=0; @Override public void run() { //省略其他耗時(shí)操作…. //使用同步代碼塊對(duì)變量i進(jìn)行同步操作,鎖對(duì)象為instance synchronized(instance){ for(int j=0;j<1000000;j++){ i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); }}/** * 輸出結(jié)果: * 2000000 */

    從代碼看出,將 synchronized 作用于一個(gè)給定的實(shí)例對(duì)象 instance,即當(dāng)前實(shí)例對(duì)象就是鎖對(duì)象,每次當(dāng)線程進(jìn)入 synchronized 包裹的代碼塊時(shí)就會(huì)要求當(dāng)前線程持有 instance 實(shí)例對(duì)象鎖,如果當(dāng)前有其他線程正持有該對(duì)象鎖,那么新到的線程就必須等待,這樣也就保證了每次只有一個(gè)線程執(zhí)行 i++;操作。當(dāng)然除了 instance 作為對(duì)象外,我們還可以使用 this 對(duì)象(代表當(dāng)前實(shí)例)或者當(dāng)前類的 class 對(duì)象作為鎖,如下代碼:

    //this,當(dāng)前實(shí)例對(duì)象鎖synchronized(this){ for(int j=0;j<1000000;j++){ i++; }}//class對(duì)象鎖synchronized(AccountingSync.class){ for(int j=0;j<1000000;j++){ i++; }}

    synchronized 禁止指令重排分析

    指令重排的情況,可以參考文章《Java 并發(fā)編程系列 1-基礎(chǔ)知識(shí)》

    我們先看如下代碼:

    class MonitorExample { int a = 0; public synchronized void writer() { //1 a++; //2 } //3 public synchronized void reader() { //4 int i = a; //5 //…… } //6}

    假設(shè)線程 A 執(zhí)行 writer()方法,隨后線程 B 執(zhí)行 reader()方法。根據(jù) happens before 規(guī)則,這個(gè)過(guò)程包含的 happens before 關(guān)系可以分為兩類:

    • 根據(jù)程序次序規(guī)則,1 happens before 2, 2 happens before 3; 4 happens before 5, 5 happens before 6。
    • 根據(jù)監(jiān)視器鎖規(guī)則,3 happens before 4。
    • 根據(jù) happens before 的傳遞性,2 happens before 5。

    上述 happens before 關(guān)系的圖形化表現(xiàn)形式如下:

    在上圖中,每一個(gè)箭頭鏈接的兩個(gè)節(jié)點(diǎn),代表了一個(gè) happens before 關(guān)系。黑色箭頭表示程序順序規(guī)則;橙色箭頭表示監(jiān)視器鎖規(guī)則;藍(lán)色箭頭表示組合這些規(guī)則后提供的 happens before 保證。

    上圖表示在線程 A 釋放了鎖之后,隨后線程 B 獲取同一個(gè)鎖。在上圖中,2 happens before 5。因此,線程 A 在釋放鎖之前所有可見(jiàn)的共享變量,在線程 B 獲取同一個(gè)鎖之后,將立刻變得對(duì) B 線程可見(jiàn)。

    synchronized 的可重入性

    從互斥鎖的設(shè)計(jì)上來(lái)說(shuō),當(dāng)一個(gè)線程試圖操作一個(gè)由其他線程持有的對(duì)象鎖的臨界資源時(shí),將會(huì)處于阻塞狀態(tài),但當(dāng)一個(gè)線程再次請(qǐng)求自己持有對(duì)象鎖的臨界資源時(shí),這種情況屬于重入鎖,請(qǐng)求將會(huì)成功。

    synchronized 就是可重入鎖,因此一個(gè)線程調(diào)用 synchronized 方法的同時(shí),在其方法體內(nèi)部調(diào)用該對(duì)象另一個(gè) synchronized 方法是允許的,如下:

    public class AccountingSync implements Runnable{ static AccountingSync instance=new AccountingSync(); static int i=0; static int j=0; @Override public void run() { for(int j=0;j<1000000;j++){ //this,當(dāng)前實(shí)例對(duì)象鎖 synchronized(this){ i++; increase();//synchronized的可重入性 } } } public synchronized void increase(){ j++; } public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); }}

    當(dāng)前實(shí)例對(duì)象鎖后進(jìn)入 synchronized 代碼塊執(zhí)行同步代碼,并在代碼塊中調(diào)用了當(dāng)前實(shí)例對(duì)象的另外一個(gè) synchronized 方法,再次請(qǐng)求當(dāng)前實(shí)例鎖時(shí),將被允許。需要特別注意另外一種情況,當(dāng)子類繼承父類時(shí),子類也是可以通過(guò)可重入鎖調(diào)用父類的同步方法。注意由于 synchronized 是基于 monitor 實(shí)現(xiàn)的,因此每次重入,monitor 中的計(jì)數(shù)器仍會(huì)加 1。

    ending

    “三妹,今天就學(xué)到這吧?!蔽曳隽朔鲅坨R對(duì)三妹說(shuō)。

    記住 synchronized 的三種應(yīng)用方式,指令重排情況分析,以及 synchronized 的可重入性,通過(guò)今天的學(xué)習(xí),你基本可以掌握 synchronized 的使用姿勢(shì),以及可能會(huì)遇到的坑。

    原文鏈接:https://mp.weixin.qq.com/s/q5QYVSgqtqNWijyp1hM-yQ

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

    相關(guān)推薦

    聯(lián)系我們

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