這篇文章帶你徹底理解synchronized關(guān)鍵字

Synchronized關(guān)鍵字一直是工作和面試中的重點(diǎn)。這篇文章準(zhǔn)備徹徹底底的從基礎(chǔ)使用到原理缺陷等各個(gè)方面來一個(gè)分析,這篇文章由于篇幅比較長,但是如果你有時(shí)間和耐心,相信會(huì)有一個(gè)比較大的收獲,所以,學(xué)習(xí)請(qǐng)慢慢來。這篇文章主要從以下幾個(gè)方面進(jìn)行分析講解.

1、Synchronized關(guān)鍵字的簡介,主要是為什么要使用Synchronized關(guān)鍵字,極其作用地位。

2、Synchronized關(guān)鍵字的使用,主要是從對(duì)象鎖和類鎖兩個(gè)角度。

3、Synchronized關(guān)鍵字的使用注意事項(xiàng)。分析了6種常見的使用情況。

4、Synchronized關(guān)鍵字的兩個(gè)性質(zhì),主要是可重入性和不可中斷性。

5、Synchronized關(guān)鍵字的底層原理。

6、Synchronized關(guān)鍵字的常見缺陷。

以上我們主要是從這7個(gè)角度來分析Synchronized關(guān)鍵字,每一個(gè)角度說實(shí)話都能單獨(dú)拿出來作為一篇文章來分析。但是由于考慮到文章的連貫性,所以綜合在了一起,循序漸進(jìn)。下面我們就帶著這些問題開始今天的文章。

一、簡介

Synchronized一句話來解釋其作用就是:能夠保證同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼,以達(dá)到并發(fā)安全的效果。也就是說Synchronized就好比是一把鎖,某個(gè)線程把資源鎖住了之后,別人就不能使用了,只有當(dāng)這個(gè)線程用完了別人才能用。

對(duì)于Synchronized關(guān)鍵字來說,它是并發(fā)編程中一個(gè)元老級(jí)角色,也就是說你只要學(xué)習(xí)并發(fā)編程,就必須要學(xué)習(xí)Synchronized關(guān)鍵字。由此可見其地位。

說了這么多,好像我們還沒體驗(yàn)過它的威力。我們就直接舉個(gè)例子,來分析一下。

public class SynTest01 implements Runnable{
    static int a=0;
    public static void main(String[] args) 
                throws InterruptedException {
        SynTest01 syn= new SynTest01();
        Thread thread1 = new Thread(syn);
        Thread thread2 = new Thread(syn);
        thread1.start();thread1.join();
        thread2.start();thread2.join();
        System.out.println(a);
    }
    @Override
    public void run() {
        for(int i=0;i<1000;i++) {
            a++;
        }
    }
}

上面代碼要完成的功能就是,thread1對(duì)a進(jìn)行增加,一直到1000,thread2再對(duì)a進(jìn)行增加,一直到2000。不過如果我們運(yùn)行過之后我們就會(huì)發(fā)現(xiàn),最后的輸出值總是小于2000,這是為什么呢?

這是因?yàn)槲覀冊趫?zhí)行a++的時(shí)候其實(shí)包含了以下三個(gè)操作:

(1)線程1讀取a

(2)線程1將a加1

(3)將a的值寫入內(nèi)存

出錯(cuò)原因的關(guān)鍵就在于第二操作和第三個(gè)操作之間,此時(shí)線程1還沒來得及把a(bǔ)的值寫入內(nèi)存,線程2就把舊值讀走了,這也就造成了a加了兩次,但是內(nèi)存中的a的值只增加了1。這也就是不同步現(xiàn)象。

但是如果說我們使用了Synchronized關(guān)鍵字之后呢?

public class SynTest01 implements Runnable{
    static int a=0;
    Object object = new Object();
    public static void main(String[] args) throws InterruptedException {
        SynTest01 syn= new SynTest01();
        Thread thread1 = new Thread(syn);
        Thread thread2 = new Thread(syn);
        thread1.start();thread1.join();
        thread2.start();thread2.join();
        System.out.println(a);
    }
    @Override
    public void run() {
        synchronized (object) {
            for(int i=0;i<1000;i++) {
                a++;
            }
        }//結(jié)束
    }
}

現(xiàn)在我們使用synchronized關(guān)鍵字把這一塊代碼鎖住,不管你怎么輸出都是2000了,鎖住之后,同一時(shí)刻只有一個(gè)線程進(jìn)入。也就不會(huì)發(fā)生上面a寫操作不同步的現(xiàn)象了。

現(xiàn)在相信你開始覺得synchronized關(guān)鍵字的確很實(shí)用,可以解決多線程中的很多問題。上面這個(gè)小例子只是帶我們?nèi)ズ唵蔚恼J(rèn)識(shí)一下,下面我們就來看看其詳細(xì)的使用。

二、使用

對(duì)于synchronized關(guān)鍵字來說,一共可以分為兩類:對(duì)象鎖和類鎖。

image

我們一個(gè)一個(gè)來看如何使用。

1、對(duì)象鎖

對(duì)于對(duì)象鎖來說,又可以分為兩個(gè),一個(gè)是方法鎖,一個(gè)是同步代碼塊鎖。

(1)同步代碼塊鎖

同步代碼塊鎖主要是對(duì)代碼塊進(jìn)行加鎖,其實(shí)已經(jīng)演示過了,就是上面的那個(gè)案例。不過為了保持一致我們再舉一個(gè)例子。

public class SynTest01 implements Runnable {
    Object object = new Object();
    public static void main(String[] args) throws InterruptedException {
        SynTest01 syn = new SynTest01();
        Thread thread1 = new Thread(syn);
        Thread thread2 = new Thread(syn);
        thread1.start();
        thread2.start();
        //線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
        while (thread1.isAlive() || thread2.isAlive()) {}
        System.out.println("main程序運(yùn)行結(jié)束");
    }
    @Override
    public void run() {
        synchronized (object) {
            try {
                System.out.println(Thread.currentThread().getName() 
                        + "線程執(zhí)行了run方法");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() 
                        + "執(zhí)行2秒鐘之后完畢");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在這個(gè)例子中,我們使用了synchronized鎖住了run方法中的代碼塊。表示同一時(shí)刻只有一個(gè)線程能夠進(jìn)入代碼塊。就好比是去醫(yī)院掛號(hào),前面一個(gè)人辦完了業(yè)務(wù),下一個(gè)人才開始。

image

在這里面我們看到,線程1和線程2使用的是同一個(gè)鎖,也就是我們new的Object。如果我們讓線程1和線程2每一個(gè)人擁有一個(gè)鎖對(duì)象呢?

public class SynTest01 implements Runnable {
    Object object1 = new Object();
    Object object2 = new Object();
    public static void main(String[] args) throws InterruptedException {
        SynTest01 syn = new SynTest01();
        Thread thread1 = new Thread(syn);
        Thread thread2 = new Thread(syn);
        thread1.start();
        thread2.start();
        //線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
        while (thread1.isAlive() || thread2.isAlive()) {}
        System.out.println("main程序運(yùn)行結(jié)束");
    }
?
    @Override
    public void run() {
        synchronized (object1) {
            try {
                System.out.println(Thread.currentThread().getName() 
                        + "線程執(zhí)行了object1");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() 
                        + "執(zhí)行object1完畢");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        synchronized (object2) {
            try {
                System.out.println(Thread.currentThread().getName() 
                        + "線程執(zhí)行object2");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() 
                        + "執(zhí)行object2完畢");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

現(xiàn)在線程1和線程2每個(gè)人擁有一把鎖,去訪問不同的方法資源。這時(shí)候會(huì)出現(xiàn)什么情況呢?

image

我們同樣用一張圖看一下其原理。

image

也就是說,相當(dāng)于兩個(gè)業(yè)務(wù)有倆窗口都可以辦理,但是兩個(gè)任務(wù)都需要排隊(duì)辦理。

同步代碼塊鎖總結(jié):

同步代碼塊鎖主要是對(duì)代碼塊進(jìn)行加鎖,此時(shí)同一時(shí)刻只能有一個(gè)線程獲取到該資源,要注意每一把鎖只負(fù)責(zé)當(dāng)前的代碼塊,其他的代碼塊不管。

以上就是同步代碼快的使用方法。下面我們看對(duì)象鎖的另外一種形式,那就是方法鎖。這里的方法鎖指代的是普通方法。

(2)方法鎖

方法鎖相比較同步代碼塊鎖就簡單很多了,就是在普通方法上添加synchronized關(guān)鍵字修飾即可。

public class SynTest2 implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        SynTest2 syn = new SynTest2();
        Thread thread1 = new Thread(syn);
        Thread thread2 = new Thread(syn);
        thread1.start();
        thread2.start();
        // 線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("main程序運(yùn)行結(jié)束");
    }
    @Override
    public void run() {
        method();
    }
    public synchronized void method() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了方法");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "執(zhí)行完畢");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在這個(gè)例子中我們使用兩個(gè)線程對(duì)同一個(gè)普通方法進(jìn)行訪問,結(jié)果可想而知,也就是同一時(shí)刻只能有一個(gè)線程進(jìn)入到此方法。我們運(yùn)行一下,看一下結(jié)果。

image

跟我們預(yù)想的一樣,很簡單。不過我們想過一個(gè)問題沒有,此時(shí)我們synchronized關(guān)鍵字加了一把鎖,這個(gè)鎖指代是誰呢?像同步代碼塊鎖synchronized (object),這里面都有object,但是方法鎖是誰呢?

答案就是this對(duì)象,也就是說我們在方法鎖里面synchronized其實(shí)鎖的就是當(dāng)前this對(duì)象。我們?nèi)绾稳ヲ?yàn)證this鎖的存在呢?不如我們再舉一個(gè)例子:

public class SynTest3 implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        SynTest3 syn = new SynTest3();
        Thread thread1 = new Thread(syn);
        Thread thread2 = new Thread(syn);
        thread1.start();
        thread2.start();
        // 線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
        while (thread1.isAlive() || thread2.isAlive()) {}
        System.out.println("main程序運(yùn)行結(jié)束");
    }
    @Override
    public void run() {
        method1();
        method2();
    }
    public synchronized void method1() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了方法1");   
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "離開方法1,并釋放鎖");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void method2() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了方法2");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "離開方法2,并釋放鎖");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面這個(gè)例子中,我們定義了兩個(gè)synchronized關(guān)鍵字修飾的方法method1和method2,然后讓兩個(gè)線程同時(shí)運(yùn)行,我們測試一下看看會(huì)出現(xiàn)什么結(jié)果:

image

從結(jié)果來看,會(huì)發(fā)現(xiàn)不管是method1還是method2,同一個(gè)時(shí)刻兩個(gè)方法只能有一個(gè)線程在運(yùn)行。這也就是this鎖導(dǎo)致的。我們再給一張圖描述一下其原理。

image

現(xiàn)在應(yīng)該明白了吧,這也就驗(yàn)證了方法鎖的存在。也驗(yàn)證了方法鎖的原理。下面我們繼續(xù)。討論一下類鎖。

2、類鎖

上面的鎖都是對(duì)象鎖,下面我們看看類鎖。類鎖其實(shí)也有兩種形式,一種是static方法鎖,一種是class鎖。

(1)static方法鎖

在java中,java的類對(duì)象可能有無數(shù)個(gè),但是類卻只有一個(gè)。首先我們看第一種形式。

public class SynTest4 implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        SynTest4 instance1 = new SynTest4();
        SynTest4 instance2 = new SynTest4();
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance2);
        thread1.start();
        thread2.start();
        System.out.println("main程序運(yùn)行結(jié)束");
    }
    @Override
    public void run() {
        method1();
    }
    public static synchronized void method1() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了靜態(tài)方法");

            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "離開靜態(tài)方法,并釋放鎖");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在這個(gè)例子中我們定義了兩個(gè)不同的對(duì)象instance1和instance2。分別去執(zhí)行了method1。會(huì)出現(xiàn)什么結(jié)果呢?

image

如果我們把static關(guān)鍵字去掉,很明顯現(xiàn)在就是普通方法了,如果我們再去運(yùn)行,由于instance1和instance2是兩個(gè)不同的對(duì)象,那么也就是兩個(gè)不同的this鎖,這時(shí)候就能隨便進(jìn)入了。我們?nèi)サ魋tatic關(guān)鍵字之后運(yùn)行一下:

image

現(xiàn)在看到了,由于是兩個(gè)不同的this鎖,所以都能進(jìn)入,就好比是一個(gè)門有兩把鑰匙,每一把都能打開門。

(2)class鎖

這種用法我們直接看例子再來分析一下:

public class SynTest5 implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        SynTest5 instance1 = new SynTest5();
        SynTest5 instance2 = new SynTest5();
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance2);
        thread1.start();
        thread2.start();
        // 線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
        while (thread1.isAlive() || thread2.isAlive()) {}
        System.out.println("main程序運(yùn)行結(jié)束");
    }
    @Override
    public void run() {
        method1();
    }
    public void method1() {
        synchronized (SynTest5.class) {
            try {
                System.out.println(Thread.currentThread().getName() + "進(jìn)入到了方法");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "離開方法");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在這個(gè)例子中我們使用了同步代碼塊,不過synchronized關(guān)鍵字包裝的可不是object了,而是SynTest5.class。我們還定義了兩個(gè)不同的對(duì)象實(shí)例instance1和instance2。運(yùn)行一下我們會(huì)發(fā)現(xiàn),線程1和線程2依然會(huì)依次執(zhí)行。

以上就是synchronized關(guān)鍵字的幾種常見的用法,到這里我們來一個(gè)總結(jié):

對(duì)于同步不同步,關(guān)鍵點(diǎn)在于鎖,兩個(gè)線程執(zhí)行的是同一把鎖,那么就依次排隊(duì)等候,兩個(gè)線程執(zhí)行的不是同一把鎖,那就各干各的事。

基本的使用我們也講完了,下面我們進(jìn)入下一個(gè)專題,那就是我們需要注意的事項(xiàng)。這是面試常考的一個(gè)問題,不管是機(jī)試還是面試。

三、6個(gè)常見的使用情況

我們先給出這6種常見的情況,然后一個(gè)一個(gè)分析。

1、兩個(gè)線程同時(shí)訪問一個(gè)對(duì)象的同步方法。

2、兩個(gè)線程訪問的是兩個(gè)對(duì)象的同步方法。

3、兩個(gè)線程訪問的是synchronized的靜態(tài)方法。

4、兩個(gè)線程同時(shí)訪問同步方法與非同步方法。

5、一個(gè)線程訪問一個(gè)類的兩個(gè)普通同步方法。

6、同時(shí)訪問靜態(tài)同步方法和非靜態(tài)同步方法。

為了對(duì)這6種情況做到心中有數(shù),不至于搞混了,我們畫一張圖,對(duì)每一種情況進(jìn)行分析。

image

上面是框架圖,下面我們基于開始來分析:

1、兩個(gè)線程同時(shí)訪問一個(gè)對(duì)象的同步方法

這種情況對(duì)應(yīng)于以下這張圖:

image

這種情況很簡單,我們在上面也演示過,結(jié)果就是同一個(gè)時(shí)刻只能有一個(gè)方法進(jìn)入。這里就不再演示了。

2、兩個(gè)線程訪問的是兩個(gè)對(duì)象的同步方法

這種情況對(duì)應(yīng)于下面這種:

image

也就是一個(gè)方法有兩把鎖,線程1和線程2互不干擾的訪問。鎖是不起作用的。

3、兩個(gè)線程訪問的是synchronized的靜態(tài)方法

這種情況對(duì)應(yīng)于下面這種情況:

image

我們對(duì)這種情況來測試一下吧。

public class SynTest6 implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        SynTest6 instance1 = new SynTest6();
        SynTest6 instance2 = new SynTest6();
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance2);
        thread1.start();
        thread2.start();
    }
    @Override
    public void run() {
        method1();
    }
    public synchronized static void method1() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了靜態(tài)方法");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "離開靜態(tài)方法,并釋放鎖");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在這個(gè)例子中我們實(shí)例化了兩個(gè)對(duì)象instance1和instance2,并且存放在了兩個(gè)不同的線程中,我們測試一下訪問同一個(gè)static同步方法你會(huì)發(fā)現(xiàn)。即使是實(shí)例不同,鎖也會(huì)生效,也就是同一時(shí)刻只能有一個(gè)線程進(jìn)去。

4、兩個(gè)線程同時(shí)訪問同步方法與非同步方法

這種情況對(duì)應(yīng)于下面這張圖:

image

我們對(duì)這種情況使用代碼進(jìn)行演示一遍:

public class SynTest7 implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        SynTest7 instance1 = new SynTest7();
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance1);
        thread1.start();
        thread2.start();
    }
    @Override
    public void run() {

        method1();
        method2();
    }
    public synchronized  void method1() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了同步方法");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "離開同步方法");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public  void method2() {
        System.out.println(Thread.currentThread().getName() + "進(jìn)入了普通方法");
        System.out.println(Thread.currentThread().getName() + "離開了普通方法");
    }
}

在上面的代碼中,我們定義一個(gè)對(duì)象,但是使用了兩個(gè)線程去分別同時(shí)訪問同步和非同步方法。我們看結(jié)果:

image

也就是說,同步方法依然會(huì)同步執(zhí)行,非同步方法不會(huì)受到任何影響。

5、一個(gè)線程訪問一個(gè)類的兩個(gè)普通同步方法

這種情況對(duì)應(yīng)于下面這張圖:

image

我們代碼來測試一下:

public class SynTest8 implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        SynTest8 instance1 = new SynTest8();
        Thread thread1 = new Thread(instance1);
        thread1.start();
    }
    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("Thread-0")) {
            method1();
        }else {
            method2();
        }
    }
    public synchronized  void method1() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了同步方法1");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "離開同步方法1");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized  void method2() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了同步方法2");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "離開同步方法2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面這個(gè)例子我們創(chuàng)建了一個(gè)對(duì)象instance1,然后使用一個(gè)線程分別去訪問同步方法1和同步方法2。結(jié)果呢可想而知,所一定會(huì)失效。因?yàn)樵谝婚_始我們已經(jīng)驗(yàn)證了,此時(shí)同步方法1和同步方法2中synchronized鎖的就是this對(duì)象,所以是同一把鎖。當(dāng)然會(huì)生效。

6、同時(shí)訪問靜態(tài)同步方法和非靜態(tài)同步方法

這種情況對(duì)應(yīng)于下面這張圖:

image

我們使用代碼來測試一波:

public class SynTest9 implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        SynTest9 instance1 = new SynTest9();
        Thread thread1 = new Thread(instance1);
        Thread thread2 = new Thread(instance1);
        thread1.start();thread2.start();
    }
    @Override
    public void run() {
            method1();
            method2();
    }
    public synchronized  void method1() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了同步方法1");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "離開同步方法1");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized static void method2() {
        try {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入到了靜態(tài)同步方法2");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "離開靜態(tài)同步方法2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在上面的代碼中,我們創(chuàng)建了一個(gè)instance實(shí)例,使用兩個(gè)線程同時(shí)訪問普通同步方法和靜態(tài)同步方法。下面運(yùn)行一下,看看輸出結(jié)果:

image

上面輸出結(jié)果表明普通同步方法和靜態(tài)同步方法是沒有關(guān)聯(lián)的,這是為什么呢?這是因?yàn)槠胀ㄍ椒椒ǖ逆i是對(duì)象,但是靜態(tài)同步方法的鎖是類,所以這是兩把鎖。鎖自然也就是失效了。

四、性質(zhì)

讀到這里,不知道你是不是已經(jīng)很疲憊了,反正我寫的是很難受,不過剩下的這些部分才是精華,也是面試或者是工作中提升你zhuangbility的一個(gè)點(diǎn)。希望你一定要注意。認(rèn)真讀下去。

對(duì)于synchronized關(guān)鍵字主要有兩個(gè)性質(zhì):可重入性質(zhì)和不可中斷性質(zhì)。我們分別來看。

1、可重入性質(zhì)

什么是可重入呢?指的是同一線程的外層函數(shù)獲得鎖之后,內(nèi)層函數(shù)可以直接再次獲取該鎖。我們舉一個(gè)例子來說明,一句話吃著碗里的看著鍋里的。嘴里面還沒吃完就繼續(xù)再去拿吃的。這就是可重入。不可重入的意思正好相反,你吃完了這碗飯才能盛下一碗。

可重入的程度可以細(xì)分為三種情況,我們分別測試一下:

(1)同一個(gè)方法中是不是可重入的。就好比是遞歸調(diào)用同步方法。

(2)不同的方法是不是可重入的。就好比是一個(gè)同步方法調(diào)用另外一個(gè)同步方法。

(3)不同的類方法是不是可重入的。

下面我們就是用代碼來測試一遍:

(1)同一個(gè)方法是不是可重入的

public class SynTest10 {
    private int a=1;
    public static void main(String[] args) throws InterruptedException {
        SynTest10 instance1 = new SynTest10();
        instance1.method1();
    }   
    public synchronized  void method1() {
        System.out.println("method1: a= " + a);
        if(a == 3) {
            return ;
        }else {
            a++;
            method1();
        }
    }
}

代碼很簡單,也就是我們定義了一個(gè)變量a,只要a不等于3,就一直遞歸調(diào)用方法method1。我們可以看一下運(yùn)行結(jié)果。

image

也就是說在同一個(gè)方法中是可重入的。下面我們接著測試。

(2)不同的方法是不是可重入的

public class SynTest10 {
    public static void main(String[] args) throws InterruptedException {
        SynTest10 instance1 = new SynTest10();
        instance1.method1();
    }   
    public synchronized  void method1() {
        System.out.println("method1");
        method2();
    }
    public synchronized  void method2() {
        System.out.println("method2" );
    }
}

我們在同步方法1中調(diào)用了同步方法2。我們同樣測試一下。

image

method1和method2可以依次輸出,說明了在不同的方法中也是可重入的。

(3)、不同的類方法是不是可重入的

既然是不同的類,那么我們就在這里定義兩個(gè)類,一個(gè)是Father,一個(gè)是Son。我們讓son調(diào)用father中的方法。

public class Father{
    public synchronized void father() {
        System.out.println("父親");
    }
}
class Son extends Father{
    public static void main(String[] args) {
        Son instance1 = new Son();
        instance1.son();
    }   
    public synchronized  void son() {
        System.out.println("兒子");
        super.father();
    }
}

在這里son類中使用super.father()調(diào)用了父類中的synchronized方法,我們測試一下看看輸出結(jié)果:

image

2、不可中斷性質(zhì)

不可中斷的意思你可以這樣理解,別人正在打游戲,你也想玩,你必須要等別人不想玩了你才能去。在java中表示一旦這個(gè)鎖被別人搶走了,你必須等待。等別的線程釋放了鎖,你才可以拿到。否則就一直等下去。

這一點(diǎn)看起來是個(gè)有點(diǎn)但其實(shí)在某些場景下弊端超級(jí)大,因?yàn)榧偃缒玫芥i得線程永遠(yuǎn)的不釋放,那你就要永遠(yuǎn)的等下去。

五、底層原理

對(duì)于原理,最好的方式就是深入到JVM中去。我們可以編譯看看其字節(jié)碼文件,再來分析,因此在這里舉一個(gè)最簡單的例子。

1、定義一個(gè)簡單例子

public class SynTest11 {
    private Object object = new Object();
    public void test() {
        synchronized(object){
            System.out.println("java的架構(gòu)師技術(shù)棧");
        }
    }

}

2、分析

分析的步驟很簡單,我們通過反編譯字節(jié)碼文件。記住我們的類名是SynTest11。

先編譯生成字節(jié)碼文件。

image

然后,我們再反編譯字節(jié)碼文件。

image

以上我們知道其是就是設(shè)置了一個(gè)監(jiān)控器monitor。線程進(jìn)來那就是monitorenter,線程離開是monitorexit。這就是synchronized關(guān)鍵字最基本的原理。

3、可重入原理

在上面我們曾提到可重入的性質(zhì),那么synchronized關(guān)鍵字是如何保證的呢?其是工作是由我們的jvm來完成的,線程第一次給對(duì)象加鎖的時(shí)候,計(jì)數(shù)為1,以后這個(gè)線程再次獲取鎖的時(shí)候,計(jì)數(shù)會(huì)依次增加。同理,任務(wù)離開的時(shí)候,相應(yīng)的計(jì)數(shù)器也會(huì)減少。

4、從java內(nèi)存模型分析

java內(nèi)存模型不是真正存在的,但是我們可以給出一個(gè)內(nèi)存模型。synchronized關(guān)鍵字,會(huì)對(duì)同步的代碼會(huì)先寫到工作內(nèi)存,等synchronized修飾的代碼塊一結(jié)束,就會(huì)寫入到主內(nèi)存,這樣保證了同步。

image

六、缺陷

synchronized關(guān)鍵字既有優(yōu)點(diǎn)也有缺點(diǎn),而且缺點(diǎn)賊多,所以后來出現(xiàn)了比他更好的鎖。下面我們就來分析一下,這也是面試常問問題。

1、效率低

我們之前曾經(jīng)分析過synchronized關(guān)鍵字是不可中斷的,這也就意味著一個(gè)等待的線程如果不能獲取到鎖將會(huì)一直等待,而不能再去做其他的事了。

這里也說明了對(duì)synchronized關(guān)鍵字的一個(gè)改進(jìn)措施,那就是設(shè)置超時(shí)時(shí)間,如果一個(gè)線程長時(shí)間拿不到鎖,就可以去做其他事情了。

2、不夠靈活

加鎖和解鎖的時(shí)候,每個(gè)鎖只能有一個(gè)對(duì)象處理,這對(duì)于目前分布式等思想格格不入。

3、無法知道是否成功獲取到鎖

也就是我們的鎖如果獲取到了,我們無法得知。既然無法得知我們也就很不容易進(jìn)行改進(jìn)。

既然synchronized有這么多缺陷。所以才出現(xiàn)了各種各樣的鎖。

七、總結(jié)

終于寫完了,synchronized涉及到的知識(shí)點(diǎn),以及能夠引出來的知識(shí)點(diǎn)超級(jí)多,不過只有理解synchronized關(guān)鍵字,我們才可以更加深入的學(xué)習(xí)。本篇文章不可能面面俱到,只能說列出來一些常見的知識(shí)點(diǎn)。更加深入的理解我也會(huì)在后續(xù)的文章中指出。感謝大家的支持。

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容