深入淺出 Java 并發(fā)編程 (1)

本文目錄

  • 故事緣由
  • synchronized 關(guān)鍵字學(xué)習(xí)
    • 什么是 synchronized ?
    • synchronized 關(guān)鍵字的作用域
    • 為什么要使用 synchronized 關(guān)鍵字啊易?
    • synchronized 關(guān)鍵字的特性
    • synchronized 同步鎖使用的優(yōu)化

故事緣由

前一陣面試完了 MSRA IEG 組的實習(xí),技術(shù)面試的時候由于我在時間內(nèi)提早寫完了代碼饮睬,所以面試官又加問了幾個問題租谈。其中一個是關(guān)于 Java 線程安全的,以前在工程上很少遇到這類問題捆愁,但是這類問題在面試的時候出現(xiàn)的概率是非常高的割去,題目不難,大概是這個樣子:

// 這段代碼是否是線程安全的昼丑?如果不是劫拗,怎么修改?

public class A {
      int a = 0;

      void funA(){
           a++;
      }

      int funB(){
           int b = a + 1;
           return b;
      }
}

這個題目是很典型的并發(fā)問題矾克,面試官叫我現(xiàn)場給他修改看看页慷,于是我把方法改成了:

public class A {
      int a = 0;

      synchronized void funA(){
           a++;
      }

      synchronized int funB(){
           int b = a + 1;
           return b;
      }
}

后來思考,感覺自己當時的回答其實不好胁附,這兩個方法里面酒繁,容易造成并發(fā)問題的其實只有一個變量 a ,考慮到程序的效率控妻, synchronized 關(guān)鍵字實際上是很重的州袒,對于這種情況可以直接使用 Java 里面自帶的一些原子類,在這里可以使用 AtomicInteger弓候,改進的代碼如下:

public class A {
      AtomicInteger a = new AtomicInteger(0);

      void funA(){
           a.incrementAndGet();
      }

      int funB() {
           int b = a.addAndGet(1);
           return b;
     }
}

在面完這個問題之后郎哭,我覺得自己在 Java 并發(fā)編程上面的基礎(chǔ)仍然薄弱,還需要系統(tǒng)的學(xué)習(xí)和提升菇存,畢竟這個還是屬于編程語言的基礎(chǔ)夸研,對于面試來說,即使其他的算法解答和項目介紹得非常好依鸥,僥幸通過了面試亥至,但是基礎(chǔ)不牢固還是沒法在今后的工作和研究中走遠。

這幾日無聊贱迟,打算重新系統(tǒng)地對 Java 并發(fā)編程進行一個復(fù)習(xí)姐扮,同時記錄在此也算當作個人的筆記,如果有不足或者是錯誤的地方衣吠,歡迎大家指正茶敏。

synchronized 關(guān)鍵字學(xué)習(xí)

什么是 synchronized ?

在介紹這個關(guān)鍵字的時候,我想先說一個生活中的場景缚俏,假如有一個衛(wèi)生間惊搏,一次只能一個人使用贮乳,如果兩個人同時擠到衛(wèi)生間里面就會出問題 (誤)。有些代碼也是一樣胀屿,在多線程的環(huán)境下,如果多個線程同時調(diào)用了某段代碼包雀,這些代碼處理的結(jié)果在不同線程里面就有可能出現(xiàn)不同步的問題宿崭。所以,我們使用了 synchronized 才写,它是一個同步鎖葡兑,它的作用就是保證同一時間代碼調(diào)用的同步性。

換句話來說赞草,synchronized 就像是衛(wèi)生間包廂前面的鎖讹堤,一個人進去了以后,他拿到了這把鎖厨疙,把自己鎖在包廂里面洲守,這樣同一個時間就只有他能夠享用衛(wèi)生間了,沒有鎖的其他人是無法訪問這個衛(wèi)生間的沾凄,就只能在衛(wèi)生間門口排隊梗醇,等待里面的那個人上完衛(wèi)生間出來,釋放鎖撒蟀,把鎖交給下一個排隊的人叙谨。

那么,說了半天保屯,什么是鎖呢手负?(感覺很抽象)
我們先來閱讀一段代碼:

public class SynchronizedUse {
    private int count = 10;
    // 鎖對象
    private Object o = new Object();

    public void m() {
        synchronized (o) { // 想要執(zhí)行下面一段代碼,必須先拿到 o 的鎖
            count--;
            System.out.println(Thread.currentThread().getName() + " count= " + count);
        }
    }
}

在這一段代碼里面姑尺,我們新建了一個 Object 對象竟终,并且使用 synchronized 作用在了了這個對象 o 上。很多人理解鎖概念的時候出現(xiàn)了誤解切蟋,認為 synchronized “鎖”住的是 synchronized 作用的代碼塊衡楞,其實這是不對的,synchronized 鎖著的是對象敦姻。想要執(zhí)行上述代碼中被 synchronized 修飾的代碼塊瘾境,只有拿到對象o的鎖才行。

由于對象只有一個镰惦,所以代碼保證了每次只會有一個線程能夠拿到這把鎖迷守,只有一個線程能夠執(zhí)行被鎖著的代碼。

synchronized 關(guān)鍵字的作用域

那我是不是每次想要同步一段代碼旺入,都得新建一個 Object 對象呢兑凿?

不是的凯力,閱讀下面這段代碼會發(fā)現(xiàn),其實我們可以直接使用 this 對象來進行代碼的鎖定礼华,這是一種簡化的寫法咐鹤。

public class SynchronizedUse02 {
    private int count = 10;

    private void m() {
        synchronized (this) {
            count--;
            System.out.println(Thread.currentThread().getName() + " count= " + count);
        }
    }
}

synchronized 關(guān)鍵字除了使用花括號{}修飾一個代碼塊(同步代碼塊)以外,還可以直接修飾一個方法圣絮,被修飾的方法也被稱為同步方法祈惶,其實和直接修飾一段代碼塊相同,修飾方法鎖定的也是 this 對象扮匠,如下面代碼所示捧请,這個寫法等同于SynchronizedUse02(上一個demo)。

public class SynchronizedUse03 {

    private int count = 10;

    public synchronized void m() { // 等同于 synchronized(this){ ...}
        count--;
        System.out.println(Thread.currentThread().getName() + " count= " + count);
    }
}

同時棒搜,synchronized 關(guān)鍵字還可以修飾一個靜態(tài)的方法疹蛉,其作用的范圍是整個靜態(tài)方法,作用的對象是這個類的所有對象力麸。了解 Java static 關(guān)鍵字的同學(xué)們都知道可款,作用在 static 方法上面的 synchronized 關(guān)鍵字實際上沒有作用在任何實例化的對象上,而是直接作用在類對象上面克蚂。如下面代碼所示:

public class SynchronizedUse04 {

    private static int count = 10;

    // synchronized 使用在靜態(tài)方法上的時候筑舅,相當于鎖定了 class
    public synchronized static void m() {
        count--;
        System.out.println(Thread.currentThread().getName() + " count= " + count);
    }

    // 相當于這個方法
    public static void mm() {
        // 實際上是反射
        synchronized (SynchronizedUse04.class) {
            count--;
        }
    }
}

這么理解,即使我實例化了不同的SynchronizedUse04對象陨舱,在不同的線程里面調(diào)用靜態(tài)方法 m() 翠拣,它仍然會保持同步,因為靜態(tài)方法是屬于類的游盲,而不是屬于對象的误墓,它對該類的所有對象都會保持同步。

為什么要使用 synchronized 關(guān)鍵字益缎?

之前說了那么多谜慌,我們一直都在強調(diào)一個"同步",那么為什么在高并發(fā)程序中莺奔,同步那么重要呢欣范,我們用一個小的demo來說明:

public class SynchronizedUse05 implements Runnable {

    private int count = 10;

    // 如果不加鎖,那么容易出現(xiàn)重復(fù)的數(shù)字,且得不到順序打印的數(shù)字令哟。
    // 每個 synchronized 的代碼塊恼琼,都代表一個原子操作,是最小的一部分屏富,不可分晴竞。
    public synchronized void run() {
        count--;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

    /*
     * main 方法里面,實際上是一個對象只啟動了一個方法狠半。
     * 但是在 for 循環(huán)里面新建了多個線程來訪問一個對象 t 噩死。
     * */
    public static void main(String[] args) {
        SynchronizedUse05 t = new SynchronizedUse05();
        for (int i = 0; i < 8; i++) {
            // 新建的8個線程都去訪問 t 里面的 run() 方法颤难。
            new Thread(t, "THREAD" + i).start();
        }
    }
}

我們在這個例子里面啟動了8個不同的線程,每個線程都會調(diào)用t里面的run()方法已维,而每個線程都對變量count進行了自減操作行嗤。源代碼為這個類的run()方法加了同步鎖,如果把鎖去掉垛耳,我們就很容易在每次之中得到不同的運行結(jié)果栅屏,或者說出現(xiàn)重復(fù)的數(shù)字,且每次運行得不到順序打印的數(shù)字艾扮,如下圖所示既琴,我們可以這樣理解:

不加同步鎖的后果

Thread 1Thread 2 幾乎同時執(zhí)行代碼占婉,它們都拿到了值為10的count并對其進行了修改泡嘴,所以這兩個線程就會輸出一樣的count,之后由于線程對于資源的搶占式得到逆济,所以陸陸續(xù)續(xù)輸出結(jié)果的線程也不會是按照順序的,這也是為什么Thread 1執(zhí)行完畢以后Thread 4接下去執(zhí)行的原因了奖慌,而加了鎖之后可以消除這樣的問題抛虫。

不加鎖的運行結(jié)果 (每次未必一致):

THREAD5 count = 4
THREAD6 count = 3
THREAD7 count = 2
THREAD3 count = 6
THREAD2 count = 7
THREAD0 count = 9
THREAD1 count = 8
THREAD4 count = 5

加了鎖的運行結(jié)果 (運行結(jié)果每次一致):

THREAD0 count = 9
THREAD7 count = 8
THREAD6 count = 7
THREAD5 count = 6
THREAD4 count = 5
THREAD3 count = 4
THREAD2 count = 3
THREAD1 count = 2

同時,對多線程讀寫代碼加上同步鎖简僧,還可以避免常見的 “臟讀問題” (Dirty Read),所謂臟讀問題,指的是對業(yè)務(wù)寫方法加鎖乃坤,對業(yè)務(wù)讀方法不加鎖枫吧,那么同一個時間寫入的線程只能有一個甥捺,但是讀取卻不受限制镀层,這樣,在寫入的同時另外一個線程進行讀取备韧,就容易讀取到錯誤的數(shù)據(jù),輕則報出空指針異常,重則讀取到匪夷所思的錯誤數(shù)據(jù)疏旨。我們可以通過下面的這個demo來體會一下:

public class SynchronizedUse07 {
    String name;
    double balance;

    private synchronized void set(String name, double balance) {
        this.name = name;

        // 寫入的時候加鎖谁榜,要是寫入時間中還在執(zhí)行一些其他的程序窃植,這時候讀程序在另外一個線程中
        // 讀取信息帝蒿,寫入工作還沒完成巷怜,就容易讀取到錯誤的信息葛超。
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.balance = balance;
    }

    private /* synchronized */ double getBalance(String name) {
        return this.balance;
    }

    public static void main(String[] args) {
        SynchronizedUse07 a = new SynchronizedUse07();

        new Thread(() -> a.set("zhangsan", 100.0)).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(a.getBalance("zhangsan"));

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(a.getBalance("zhangsan"));
    }
}

在上述代碼中暴氏,我們把讀取賬戶余額的方法 getBalance(String name) 上面的同步鎖 synchronized 注釋掉了,保留了寫入(初始化)方法的同步鎖绣张,這樣運行就會產(chǎn)生臟讀問題答渔。為了讓代碼問題突出,我們在set()里面加入了一段延時程序:

     try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

在業(yè)務(wù)邏輯中侥涵,寫入數(shù)據(jù)可能是一個耗時操作(這里我們使用了2秒的延時),而如果讀取操作不上鎖沼撕,在寫入操作還未完成的時候就開始讀取,就會讀取到錯誤數(shù)據(jù)(這里我們在延時1秒后開始讀取芜飘,這時候?qū)懭氩僮魃形赐瓿桑┪癫颍缘谝淮挝覀冏x取到的數(shù)據(jù)是 0.0(未初始化值),而當寫入操作完成以后嗦明,我們再次進行讀取操作,這時候才讀取到正確的數(shù)據(jù) 100.0招狸。

程序輸出結(jié)果如下:

0.0 // 1秒的時候開始讀取敬拓,寫入未完成
100.0 // 3秒的時候讀取累榜,寫入(耗時約2秒)已完成

通過上面兩個例子猖凛,我們現(xiàn)在對 synchronized 關(guān)鍵字的作用和用法有了一個初步的了解。

synchronized 關(guān)鍵字的特性

synchronized 關(guān)鍵字修飾的同步方法能不能和非同步方法同時調(diào)用?

從之前臟讀問題的demo可以很輕易地得到答案:可以郊酒。
為此我們編寫了一個demo杠纵,感興趣的朋友可以嘗試運行體會一下:

public class SynchronizedUse06 {
    // m1 是同步方法银亲,請問在執(zhí)行m1 的過程之中馏段,m2能不能被執(zhí)行晕翠? 回答:當然可以
    private synchronized void m1() {
        System.out.println(Thread.currentThread().getName() + " m1 start...");

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " m1 end.");
    }

    private void m2() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " m2.");
    }

    public static void main(String[] args) {
        SynchronizedUse06 t = new SynchronizedUse06();

//        new Thread(() -> t.m1(), "t1").start();
//        new Thread(() -> t.m2(), "t1").start();

        new Thread(t::m1, "t1:").start();
        new Thread(t::m2, "t2:").start();
    }
}

這里的m1()是一個同步方法樊卓,其中我們?yōu)樗尤肓艘粋€長達10秒的延時七扰,m2()是一個非同步方法。我們新建了兩個線程毕箍,線程t1在運行方法m1()的時候,我們啟動線程t2運行方法m2()涩澡,可以看到粥帚,m1()m2()是可以同時運行的:

t1: m1 start...
t2: m2.
t1: m1 end.

在啟動線程這里我們使用了 Java8 的新特新:Lambda表達式,這樣的作用主要是簡化寫法(語法糖),感興趣的同學(xué)可以另外了解速警。

對于同步方法來說,它可以和非同步方法同時調(diào)用,那么對于同步方法之間的相互調(diào)用來說淮阐,這是否可行群扶?

我們先來看一段小程序:

/**
 * synchronized 關(guān)鍵字的使用08
 * 一個同步方法可以調(diào)用另外一個同步方法
 * 一個線程已經(jīng)擁有某個對象的鎖,再次申請的時候仍然會得到該對象的鎖蒸走。
 * 也就是說 synchronized 獲得的鎖是可以重入的。
 *
 * @author huangyz0918
 */
public class SynchronizedUse08 {
    private synchronized void m1() {
        System.out.println("m1 start...");

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        m2();
    }

    private synchronized void m2() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("m2.");
    }

    public static void main(String[] args) {
        SynchronizedUse08 t = new SynchronizedUse08();
        t.m1();
    }
}

運行結(jié)果:

m1 start...
m2.

在代碼中,我們調(diào)用了同步方法m1()m2()也得到了調(diào)用礁哄,說明同步方法直接是可以相互調(diào)用的蚀乔。方法m1()m2()需要的是同一把鎖,所以當m2()m1()中被調(diào)用的時候衔峰,m1()在持有鎖的前提下再次申請獲得這把鎖來執(zhí)行m2()歇盼,這是可以的,因為synchronized獲得的鎖是可以重入的帘不。

那么對于繼承的同步方法來說说莫,子類方法是否也是同步方法?

不是的寞焙,synchronized 關(guān)鍵字不能被繼承储狭。

雖然可以使用synchronized 來定義方法,但 synchronized 并不屬于方法定義的一部分捣郊,因此晶密,synchronized 關(guān)鍵字不能被繼承。如果在父類中的某個方法使用了 synchronized 關(guān)鍵字模她,而在子類中覆蓋了這個方法稻艰,在子類中的這個方法默認情況下并不是同步的,而必須顯式地在子類的這個方法中加上synchronized關(guān)鍵字才可以侈净。當然尊勿,還可以在子類方法中調(diào)用父類中相應(yīng)的方法,這樣雖然子類中的方法不是同步的畜侦,但子類調(diào)用了父類的同步方法元扔,因此,子類的方法也就相當于同步了旋膳。具體可以見下面的 demo:

public class SynchronizedUse09 {

    public static void main(String[] args) {
        T t = new T();
        t.m();
    }

    synchronized void m() {
        System.out.println("m start...");

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("m end.");
    }
}

class T extends SynchronizedUse09 {
    @Override
    synchronized void m() {
        System.out.println("child m start...");
        super.m();
        System.out.println("child m end.");
    }
}

這里的m()顯式地加上了關(guān)鍵字synchronized涮俄,所以它也是一個同步方法掀亥。如果不加這個關(guān)鍵字,m()就不會得到同步,當然轴脐,在調(diào)用到父類super.m()的時候,仍然需要獲得鎖才能夠執(zhí)行栖忠,否則方法將一直在super.m()上面等待蝌戒。

那么對于一個程序,在獲得了鎖開始執(zhí)行的時候碱工,忽然在同步代碼塊里面出現(xiàn)了異常娃承,跳出了同步代碼奏夫,那么鎖是否會釋放?

程序在執(zhí)行的過程中历筝,如果出現(xiàn)異常酗昼,默認情況下鎖會被釋放。

我們可以試著建立一個 demo 研究一下:

public class SynchronizedUse10 {
    int count = 0;

    synchronized void m() {
        System.out.println(Thread.currentThread().getName() + " start...");
        while (true) {
            count++;
            System.out.println(Thread.currentThread().getName() + " count = " + count);

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 5) {
                int i = 1 / 0; // 此處拋出異常梳猪,鎖將被釋放麻削,若是不想鎖被釋放可以進行 catch 使循環(huán)繼續(xù)。
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedUse10 t = new SynchronizedUse10();
        new Thread(t::m, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(t::m, "t2").start();
    }
}

運行結(jié)果:

t1 start...
t1 count = 1
t1 count = 2
t1 count = 3
t1 count = 4
t1 count = 5
t2 start...
Exception in thread "t1" java.lang.ArithmeticException: / by zero
t2 count = 6
    at learn.multithreading.synchronizedlearn.SynchronizedUse10.m(SynchronizedUse10.java:32)
    at java.base/java.lang.Thread.run(Thread.java:844)
t2 count = 7
t2 count = 8
t2 count = 9
t2 count = 10
t2 count = 11
t2 count = 12

這里我們使用了一個死循環(huán)舔示,在count == 5地時候碟婆,我們手動的拋出了一個異常,使得線程t1從同步方法m()中跳出惕稻,并且釋放了鎖竖共,我們可以看到,在t1拋出異常以后俺祠,t2迅速獲得了鎖并且開始執(zhí)行公给。說明程序在執(zhí)行的過程中,如果出現(xiàn)異常蜘渣,默認情況下鎖會被釋放淌铐。 所以,在并發(fā)處理的過程中蔫缸,如果出現(xiàn)了異常一定要多加小心腿准,不然可能會發(fā)生不一致的情況。比如在一個 Web App 處理的過程中拾碌,多個 servlet 線程共同訪問一個資源吐葱,這時候如果異常處理不合適, 在第一個線程里面拋出異常校翔,其他線程就會進入同步代碼區(qū)弟跑,有可能會訪問到異常時產(chǎn)生的數(shù)據(jù)。

synchronized 同步鎖使用的優(yōu)化

雖然現(xiàn)在 Java 已經(jīng)對 synchronized 鎖進行了很大幅度的優(yōu)化防症,不過相對與其他同步機制來說孟辑,synchronized 的效率還是比較低的,所以在使用這個關(guān)鍵字的時候蔫敲,我們要注意鎖的粒度饲嗽,避免不必要的計算資源浪費。

舉個例子:

public class SynchronizedUse11 {
    private int count = 0;

    synchronized void m1() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        count++; // 業(yè)務(wù)邏輯中只有這句話需要同步燕偶,這時候不需要給整個方法上鎖喝噪。

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    void m2() {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 業(yè)務(wù)邏輯中只有這一段語句需要同步,這時不應(yīng)該給整個方法上鎖
        // 采用細粒度的鎖指么,可以使線程爭用的時間變短酝惧,從而提高效率。
        synchronized (this) {
            count++;
        }

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在這里伯诬,我們只有count++ 不符合原子性晚唇,所以容易出現(xiàn)同步問題,沒有必要對整個方法進行上鎖盗似,所以說synchronized關(guān)鍵字的使用還是很靈活的哩陕,在編碼的時候要時時刻刻考慮到效率的問題。

同時赫舒,我們應(yīng)該理解synchronized鎖定的本質(zhì)悍及,synchronized其實鎖定的是堆內(nèi)存中的對象,所以當一個鎖定的對象的屬性發(fā)生了改變接癌,或者說心赶,鎖定對象的引用指向了堆內(nèi)存中的一個新的對象的時候,鎖也會改變缺猛。在實際使用當中缨叫,我們應(yīng)該避免發(fā)生這樣的情況:

public class SynchronizedUse12 {

    public static void main(String[] args) {
        SynchronizedUse12 t = new SynchronizedUse12();
        new Thread(t::m, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread t2 = new Thread(t::m, "t2");

        t.o = new Object(); // 鎖的對象改變,所以 t2 得以執(zhí)行荔燎,不然 t2 永遠得到不了執(zhí)行的機會耻姥。
        t2.start();
    }

    Object o = new Object();

    void m() {
        synchronized (o) { // 本質(zhì)上說明了: 鎖的位置是鎖在堆內(nèi)存的對象上,而不是棧內(nèi)存對象的引用里面有咨。
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName());
            }
        }
    }
}

輸出結(jié)果:

t1
t1
t2 // 從這里開始琐簇,t2線程也開始了運行
t1
t2
t1
t2
t1
....

在這個小程序里面,我們可以看到我們使用t.o = new Object();將鎖的對象指向了堆內(nèi)存里面的一個新的對象座享,這個時候線程t2其實已經(jīng)和t1所需要的不是同一把鎖了婉商,所以線程t2也開始運行。不然處于死循環(huán)方法m()里面征讲,不等到t1執(zhí)行完成据某,t2永遠也得不到執(zhí)行的機會。

最后诗箍,我們在實際的開發(fā)過程中癣籽,經(jīng)常要盡力避免使用字符串對象進行鎖定。為什么呢滤祖?如果你使用了某個類庫筷狼,它鎖定了字符串對象 A ,這時候你在自己的源代碼里面又鎖定了字符串對象 A 匠童,兩段代碼不經(jīng)意之間使用了同一把鎖埂材,這樣就會出現(xiàn)很詭異的死鎖阻塞,而且對于你來說這樣的問題很難被排查出來汤求。并且對于字符串來說俏险,兩個相同的字符串其實指向的是同一個內(nèi)存地址严拒,所以看似使用的不是同一把鎖,實際上不然:

public class SynchronizedUse13 {

    private String s1 = "Hello";
    private String s2 = "Hello";

    // 兩個字符串s1和s2實際上指向了同一個堆內(nèi)存的對象
    // 棧內(nèi)存里面存放原始變量和對象的引用句柄
    // 堆內(nèi)存里面存放的是對象的實例

    void m1() {
        synchronized (s1) {
        }
    }

    void m2() {
        synchronized (s2) {
        }
    }
}

總之竖独,Java 同步鎖 synchronized 的使用是很靈活的裤唠,需要在實踐中不斷總結(jié)和反復(fù)記憶。

相關(guān)閱讀:

本教程純屬原創(chuàng)莹痢,轉(zhuǎn)載請聲明
本文提供的鏈接若是失效請及時聯(lián)系作者更新

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末种蘸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子竞膳,更是在濱河造成了極大的恐慌航瞭,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坦辟,死亡現(xiàn)場離奇詭異刊侯,居然都是意外死亡,警方通過查閱死者的電腦和手機长窄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門滔吠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挠日,你說我怎么就攤上這事疮绷。” “怎么了嚣潜?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵冬骚,是天一觀的道長。 經(jīng)常有香客問我懂算,道長只冻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任计技,我火速辦了婚禮喜德,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘垮媒。我一直安慰自己舍悯,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布睡雇。 她就那樣靜靜地躺著萌衬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪它抱。 梳的紋絲不亂的頭發(fā)上秕豫,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音观蓄,去河邊找鬼混移。 笑死祠墅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的沫屡。 我是一名探鬼主播饵隙,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼撮珠,長吁一口氣:“原來是場噩夢啊……” “哼沮脖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起芯急,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤勺届,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后娶耍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體免姿,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年榕酒,在試婚紗的時候發(fā)現(xiàn)自己被綠了胚膊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡想鹰,死狀恐怖紊婉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辑舷,我是刑警寧澤喻犁,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站何缓,受9級特大地震影響肢础,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碌廓,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一传轰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谷婆,春花似錦慨蛙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廷区,卻和暖如春唯灵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背隙轻。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工埠帕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垢揩,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓敛瓷,卻偏偏與公主長得像叁巨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子呐籽,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容