對synchronized的一點理解

定義

Java中具有通過synchronized實現的內置鎖,內置鎖獲取鎖和釋放鎖的過程是隱式的,進入synchronized修飾的代碼就獲得鎖,離開相應的代碼就釋放鎖。

作用

當它用來修飾一個方法或一個代碼塊時,能夠保證在同一時刻最多只有一個線程執行該代碼。

使用

synchronized主要有兩種使用方法:synchronized方法和synchronized代碼塊。

  • synchronized方法:
public synchronized void foo1() {
    System.out.println("synchronized methoed");
}

注意:synchronized方法只能保證被修飾的方法為互斥訪問,而不能保證未被synchronized方法互斥訪問。

public class SynchronizedMethod {
    //synchronized方法A
    public synchronized void synchronizedMethodA() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodA");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodA");
    }
    //synchronized方法B
    public synchronized void synchronizedMethodB() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodB");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodB");
    }
    //非synchronized方法
    public void notSynchronizedMethod() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in notSynchronizedMethod");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out notSynchronizedMethod");
    }
}

public class SynchronizedTest {

    public static void main(String[] args){
        //設置兩個對象
        SynchronizedMethod method = new SynchronizedMethod();
        SynchronizedMethod anotherMethod = new SynchronizedMethod();
        Thread t1 = new Thread(new MyRunnable(method,0), "thread1");
        Thread t2 = new Thread(new MyRunnable(method,1), "thread2");
        Thread t3 = new Thread(new MyRunnable(method,2), "thread3");
        //使用另一個對象作為對比
        Thread t4 = new Thread(new MyRunnable(anotherMethod,0), "thread4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

    static class MyRunnable implements Runnable{
        private SynchronizedMethod method;
        private int idx;
        public MyRunnable(SynchronizedMethod method, int idx){
            this.method = method;
            this.idx = idx;
        }
        @Override
        public void run() {
            try {
                if (idx %3 == 0)
                    method.synchronizedMethodA();
                else if (idx % 3 == 1)
                    method.notSynchronizedMethod();
                else
                    method.synchronizedMethodB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
輸出結果:
thread1 enter in synchronizedMethodA
thread2 enter in notSynchronizedMethod
thread4 enter in synchronizedMethodA
thread4 get out synchronizedMethodA
thread1 get out synchronizedMethodA
thread2 get out notSynchronizedMethod
thread3 enter in synchronizedMethodB
thread3 get out synchronizedMethodB

如上,根據結果,對比線程1和線程3可以知道synchronized方法保證了同一對象的互斥訪問。對比線程1和線程2可以知道synchronized方法只會保證synchronized修飾的互斥訪問。對比線程1和4可以知道,synchronized方法為對象鎖,不能保證不同對象的互斥訪問。

  • synchronized代碼塊:
public void foo2() {
    synchronized (this) {
        System.out.println("synchronized methoed");
    }
}

synchronized代碼塊中的this是指當前對象。也可以將this替換成其他對象,例如將list替換成obj,則foo2()在執行synchronized(obj)時獲取的就是obj的同步鎖。

public class InsertData {
    private ArrayList<Integer> list1 = new ArrayList<>();
    private ArrayList<Integer> list2 = new ArrayList<>();

    public void insertData1(Thread t){
        synchronized (this){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list1數據" + i);
                list1.add(i);
            }
        }
    }

    public void insertData2(Thread t){
        synchronized (this){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list2數據" + i);
                list2.add(i);
            }
        }
    }
}

public class SychronizedTest2 {

    public static void main(String[] args){
        InsertData insertData = new InsertData();
        Thread t1 = new Thread(new MyRunnable(insertData,0),"thread1");
        Thread t2 = new Thread(new MyRunnable(insertData,1),"thread2");
        t1.start();
        t2.start();
    }

    static class MyRunnable implements Runnable{
        private InsertData insertData;
        private int idx;

        public MyRunnable(InsertData insertData, int idx){
            this.insertData = insertData;
            this.idx = idx;
        }

        @Override
        public void run() {
            if (idx % 2 == 0)
                insertData.insertData1(Thread.currentThread());
            else
                insertData.insertData2(Thread.currentThread());
        }
    }

}
輸出結果:
thread2在插入list2數據0
thread2在插入list2數據1
thread2在插入list2數據2
thread2在插入list2數據3
thread2在插入list2數據4
thread1在插入list1數據0
thread1在插入list1數據1
thread1在插入list1數據2
thread1在插入list1數據3
thread1在插入list1數據4

根據結果,我們可以看到兩個線程時互斥訪問InsertData對象的。如果我們只是希望list1和list2分別被互斥訪問,而不是互斥訪問InsertData對象,那么可以修改如下:

public class InsertData {
    private ArrayList<Integer> list1 = new ArrayList<>();
    private ArrayList<Integer> list2 = new ArrayList<>();

    public void insertData1(Thread t){
        synchronized (list1){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list1數據" + i);
                list1.add(i);
            }
        }
    }

    public void insertData2(Thread t){
        synchronized (list2){
            for (int i = 0; i < 5; i ++){
                System.out.println(t.getName() + "在插入list2數據" + i);
                list2.add(i);
            }
        }
    }
}

輸出結果:
thread1在插入list1數據0
thread2在插入list2數據0
thread1在插入list1數據1
thread2在插入list2數據1
thread1在插入list1數據2
thread2在插入list2數據2
thread1在插入list1數據3
thread2在插入list2數據3
thread1在插入list1數據4
thread2在插入list2數據4

根據結果,我們可以看到兩個線程是并行的。

建議:盡量使用synchronized代碼塊,因為synchronized代碼塊可以更精確地控制沖突限制訪問區域,有時候表現地更高效。

public class SynchronizedTest3 {

    public synchronized void synMethod(){
        for (int i = 0; i < 1000000; i ++);
    }

    public void synBlock(){
        synchronized (this){
            for (int i = 0; i < 1000000; i ++);
        }
    }

    public static void main(String[] args){
        SynchronizedTest3 test3 = new SynchronizedTest3();
        long start,end;
        start = System.currentTimeMillis();
        test3.synMethod();
        end = System.currentTimeMillis();
        System.out.println("synMethod() takes " + (end - start) + " ms");
        start = System.currentTimeMillis();
        test3.synBlock();
        end = System.currentTimeMillis();
        System.out.println("synBlock() takes " + (end - start) + " ms");
    }

}

輸出結果:
synMethod() takes 3 ms
synBlock() takes 2 ms

實例鎖和全局鎖

  • 實例鎖:鎖在某一個實例對象上。如果類是單例,那么該鎖也具有全局鎖的概念。其對應的是synchronized關鍵字。
  • 全局鎖:該鎖針對的是類,無論實例多少個對象,線程都共享該鎖。全局鎖對應的是static synchronized。

我們先來驗證實例鎖:對于同一實例必須互斥訪問,而不同實例是可以并行。

public class SynchronizedMethod {
    //synchronized方法A
    public synchronized void synchronizedMethodA() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodA");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodA");
    }
    //static synchronized方法B
    public static synchronized void synchronizedMethodB() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodB");
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodB");
    }
}

public class SynchronizedTest {

    public static void main(String[] args){
        //設置兩個對象
        SynchronizedMethod method = new SynchronizedMethod();
        SynchronizedMethod anotherMethod = new SynchronizedMethod();
        //驗證synchronized
        Thread t1 = new Thread(new MyRunnable(method), "thread1");
        Thread t2 = new Thread(new MyRunnable(method), "thread2");
        Thread t3 = new Thread(new MyRunnable(anotherMethod), "thread3");
        //驗證static synchronized
        //Thread t4 = new Thread(new MyRunnable(method,1), "thread4");
        //Thread t5 = new Thread(new MyRunnable(anotherMethod,1), "thread5");
        t1.start();
        t2.start();
        t3.start();
        //t4.start();
        //t5.start();
    }

    static class MyRunnable implements Runnable{
        private SynchronizedMethod method;
        public MyRunnable(SynchronizedMethod method){
            this.method = method;
        }
        @Override
        public void run() {
            try {
                    method.synchronizedMethodA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

輸出結果:
thread3 enter in synchronizedMethodA
thread1 enter in synchronizedMethodA
thread3 get out synchronizedMethodA
thread1 get out synchronizedMethodA
thread2 enter in synchronizedMethodA
thread2 get out synchronizedMethodA

我們可以看到線程1和3為不同實例,因此可以并行處理,而線程1和2是同一個實例,必須互斥訪問。

接下來,我們再來驗證全局鎖

public class SynchronizedTest {

    public static void main(String[] args){
        //設置兩個對象
        SynchronizedMethod method = new SynchronizedMethod();
        SynchronizedMethod anotherMethod = new SynchronizedMethod();
        //驗證static synchronized
        Thread t4 = new Thread(new MyRunnable(method), "thread4");
        Thread t5 = new Thread(new MyRunnable(anotherMethod), "thread5");
        t4.start();
        t5.start();
    }

    static class MyRunnable implements Runnable{
        private SynchronizedMethod method;
        public MyRunnable(SynchronizedMethod method){
            this.method = method;
        }
        @Override
        public void run() {
            try {
                    method.synchronizedMethodB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

輸出結果:
thread5 enter in synchronizedMethodB
thread5 get out synchronizedMethodB
thread4 enter in synchronizedMethodB
thread4 get out synchronizedMethodB

我們可以看到雖然線程4和5為不同實例,但兩者卻無法進行并行處理。

基本原則

  • 當一個線程訪問“某對象”的synchronized方法“或者”synchronized代碼塊“時,其他線程對”該對象“的”synchronized方法“或者”synchronized代碼塊“將被阻塞。
  • 當一個線程訪問”某對象“的”synchronized方法“或者”synchronized方法塊“時,其他線程可以訪問”該對象“的非同步代碼塊。
  • 當一個線程訪問”某對象“的”synchronized方法“或者”synchronized方法塊“時,其他線程對”該對象“的其他的“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。

synchronized原理

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容