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

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

1嚣鄙、Synchronized關(guān)鍵字的簡(jiǎn)介,主要是為什么要使用Synchronized關(guān)鍵字唆姐,極其作用地位拗慨。

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

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

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

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

6摩渺、Synchronized關(guān)鍵字的常見(jiàn)缺陷简烤。

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

一、簡(jiǎn)介

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

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

說(shuō)了這么多旁涤,好像我們還沒(méi)體驗(yàn)過(guò)它的威力翔曲。我們就直接舉個(gè)例子,來(lái)分析一下劈愚。

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。不過(guò)如果我們運(yùn)行過(guò)之后我們就會(huì)發(fā)現(xiàn)注祖,最后的輸出值總是小于2000猾蒂,這是為什么呢?

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

(1)線程1讀取a

(2)線程1將a加1

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

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

但是如果說(shuō)我們使用了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)在相信你開(kāi)始覺(jué)得synchronized關(guān)鍵字的確很實(shí)用,可以解決多線程中的很多問(wèn)題拿诸。上面這個(gè)小例子只是帶我們?nèi)ズ?jiǎn)單的認(rèn)識(shí)一下扒袖,下面我們就來(lái)看看其詳細(xì)的使用。

二亩码、使用

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

image

我們一個(gè)一個(gè)來(lái)看如何使用描沟。

1飒泻、對(duì)象鎖

對(duì)于對(duì)象鎖來(lái)說(shuō),又可以分為兩個(gè)吏廉,一個(gè)是方法鎖泞遗,一個(gè)是同步代碼塊鎖。

(1)同步代碼塊鎖

同步代碼塊鎖主要是對(duì)代碼塊進(jìn)行加鎖席覆,其實(shí)已經(jīng)演示過(guò)了史辙,就是上面的那個(gè)案例。不過(guò)為了保持一致我們?cè)倥e一個(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è)人才開(kāi)始孤荣。

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è)人擁有一把鎖,去訪問(wèn)不同的方法資源遂庄。這時(shí)候會(huì)出現(xiàn)什么情況呢寥院?

image

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

image

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

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

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

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

(2)方法鎖

方法鎖相比較同步代碼塊鎖就簡(jiǎn)單很多了冷尉,就是在普通方法上添加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)行訪問(wèn)系枪,結(jié)果可想而知雀哨,也就是同一時(shí)刻只能有一個(gè)線程進(jìn)入到此方法。我們運(yùn)行一下私爷,看一下結(jié)果雾棺。

image

跟我們預(yù)想的一樣,很簡(jiǎn)單衬浑。不過(guò)我們想過(guò)一個(gè)問(wèn)題沒(méi)有捌浩,此時(shí)我們synchronized關(guān)鍵字加了一把鎖,這個(gè)鎖指代是誰(shuí)呢工秩?像同步代碼塊鎖synchronized (object)尸饺,這里面都有object,但是方法鎖是誰(shuí)呢助币?

答案就是this對(duì)象侵佃,也就是說(shuō)我們?cè)诜椒ㄦi里面synchronized其實(shí)鎖的就是當(dāng)前this對(duì)象。我們?nèi)绾稳ヲ?yàn)證this鎖的存在呢奠支?不如我們?cè)倥e一個(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() + "離開(kāi)方法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() + "離開(kāi)方法2抚芦,并釋放鎖");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

image

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

image

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

2惫叛、類鎖

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

(1)static方法鎖

在java中仑最,java的類對(duì)象可能有無(wú)數(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() + "離開(kāi)靜態(tài)方法,并釋放鎖");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

image

如果我們把static關(guān)鍵字去掉葫笼,很明顯現(xiàn)在就是普通方法了深啤,如果我們?cè)偃ミ\(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è)門有兩把鑰匙友绝,每一把都能打開(kāi)門堤尾。

(2)class鎖

這種用法我們直接看例子再來(lái)分析一下:

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() + "離開(kāi)方法");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

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

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

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

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

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

我們先給出這6種常見(jiàn)的情況嗡害,然后一個(gè)一個(gè)分析焚碌。

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

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

3叹螟、兩個(gè)線程訪問(wèn)的是synchronized的靜態(tài)方法鹃骂。

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

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

6良价、同時(shí)訪問(wèn)靜態(tài)同步方法和非靜態(tài)同步方法寝殴。

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

image

上面是框架圖痊银,下面我們基于開(kāi)始來(lái)分析:

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

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

image

這種情況很簡(jiǎn)單,我們?cè)谏厦嬉惭菔具^(guò)溯革,結(jié)果就是同一個(gè)時(shí)刻只能有一個(gè)方法進(jìn)入泌射。這里就不再演示了。

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

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

image

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

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

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

image

我們對(duì)這種情況來(lái)測(cè)試一下吧。

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() + "離開(kāi)靜態(tài)方法臭猜,并釋放鎖");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

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

這種情況對(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() + "離開(kāi)同步方法");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public  void method2() {
        System.out.println(Thread.currentThread().getName() + "進(jìn)入了普通方法");
        System.out.println(Thread.currentThread().getName() + "離開(kāi)了普通方法");
    }
}

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

image

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

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

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

image

我們代碼來(lái)測(cè)試一下:

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() + "離開(kāi)同步方法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() + "離開(kāi)同步方法2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

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

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

image

我們使用代碼來(lái)測(cè)試一波:

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() + "離開(kāi)同步方法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() + "離開(kāi)靜態(tài)同步方法2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

image

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

四问芬、性質(zhì)

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

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

1道媚、可重入性質(zhì)

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

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

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

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

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

下面我們就是用代碼來(lái)測(cè)試一遍:

(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();
        }
    }
}

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

image

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

(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" );
    }
}

我們?cè)谕椒椒?中調(diào)用了同步方法2化借。我們同樣測(cè)試一下潜慎。

image

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

(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方法鳖悠,我們測(cè)試一下看看輸出結(jié)果:

image

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

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

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

五呆贿、底層原理

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

1竟块、定義一個(gè)簡(jiǎn)單例子

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

}

2壶运、分析

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

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

image

然后耸携,我們?cè)俜淳幾g字節(jié)碼文件棵癣。

image

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

3刷后、可重入原理

在上面我們?cè)岬娇芍厝氲男再|(zhì)的畴,那么synchronized關(guān)鍵字是如何保證的呢?其是工作是由我們的jvm來(lái)完成的尝胆,線程第一次給對(duì)象加鎖的時(shí)候丧裁,計(jì)數(shù)為1,以后這個(gè)線程再次獲取鎖的時(shí)候含衔,計(jì)數(shù)會(huì)依次增加煎娇。同理二庵,任務(wù)離開(kāi)的時(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)賊多,所以后來(lái)出現(xiàn)了比他更好的鎖裆操。下面我們就來(lái)分析一下怒详,這也是面試常問(wèn)問(wèn)題。

1跷车、效率低

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

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

2、不夠靈活

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

3薪鹦、無(wú)法知道是否成功獲取到鎖

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

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

七地熄、總結(jié)

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

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末椿浓,一起剝皮案震驚了整個(gè)濱河市顽染,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轰绵,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尼荆,死亡現(xiàn)場(chǎng)離奇詭異左腔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)捅儒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門液样,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人巧还,你說(shuō)我怎么就攤上這事鞭莽。” “怎么了麸祷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵澎怒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我阶牍,道長(zhǎng)喷面,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任走孽,我火速辦了婚禮惧辈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘磕瓷。我一直安慰自己盒齿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布困食。 她就那樣靜靜地躺著边翁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陷舅。 梳的紋絲不亂的頭發(fā)上倒彰,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音莱睁,去河邊找鬼待讳。 笑死芒澜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的创淡。 我是一名探鬼主播痴晦,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼琳彩!你這毒婦竟也來(lái)了誊酌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤露乏,失蹤者是張志新(化名)和其女友劉穎碧浊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體瘟仿,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡箱锐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劳较。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驹止。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖观蜗,靈堂內(nèi)的尸體忽然破棺而出臊恋,到底是詐尸還是另有隱情,我是刑警寧澤墓捻,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布抖仅,位于F島的核電站,受9級(jí)特大地震影響砖第,放射性物質(zhì)發(fā)生泄漏岸售。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一厂画、第九天 我趴在偏房一處隱蔽的房頂上張望凸丸。 院中可真熱鬧,春花似錦袱院、人聲如沸屎慢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腻惠。三九已至,卻和暖如春欲虚,著一層夾襖步出監(jiān)牢的瞬間集灌,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欣喧,地道東北人腌零。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像唆阿,于是被迫代替她去往敵國(guó)和親益涧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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