線程篇3:[-synchronized-]

零、前言

本文按照慕課網免費課程敲的,同時也加入了我大量的思考和繪圖,但願對你有所幫助java


1、多線程的簡單回顧

1.入門級

下面WhatIsWrong實現Runnable,並提供一個靜態實例對象計時器i
run方法讓i自加10W次,下面的結果是多少?git

public class WhatIsWrong implements Runnable {
    static WhatIsWrong instance = new WhatIsWrong();
    static int i = 0;
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        System.out.println(i);
    }
    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            i++;
        }
    }
}
複製代碼

答案是0,簡單講一下:main中代碼順序執行雖然線程1,2都開啓了,
可是程序仍是順序執行的,會馬上走System.out.println(i);,實際看來run方法要慢一些github

簡單分析.png


2.如何讓打印在兩個線程完成後才調用

兩個方法:1)讓主線程先睡一會、2)使用線程對象的join方法
總之就是推遲System.out.println(i);的執行時間編程

2.1:讓主線程先睡一會

這個方法很容易想到,但睡多久很差把握,通常小測試1s應該夠了安全

線程休眠.png

public class WhatIsWrong implements Runnable {
    static WhatIsWrong instance = new WhatIsWrong();
    static int i = 0;

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }

    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            i++;
        }
    }
}
複製代碼

2.2.join方法

正規的仍是用join吧,他會讓該線程先行,使以System.out.println(i);被推後bash

線程join.png

public class WhatIsWrong implements Runnable {
    static WhatIsWrong instance = new WhatIsWrong();
    static int i = 0;

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }

    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            i++;
        }
    }
}

複製代碼

3.結果呢?
3.1下面是十次結果
137355  、 114412 、115381 、128482 、151021 、
109093 、  128610 、128144 、122390 、123746
複製代碼

3.2從中能看出什麼?

1).每次執行結果都不同
2).它們都大於100000且小於200000微信


3.3爲何

理論上兩個線程,每一個線程加100000次,一共應該200000纔對
想象一下這個場景:多線程

有兩個神槍手對着靶子打(必中),每把槍有100000顆軟子彈
靶子有感應器和計數器,當軟子彈接觸靶子那一刻,計數器加1  
當兩個子彈同時接觸時,感應器有沒法及時反應,只會被記錄一次,即計數器只+1
而後兩人瘋狂掃射,最後看靶子上計數器最終的數值  

可想而知最後計數器上的數應該是小於200000的,因此代碼中也相似
兩個線程即是神槍手,run的時候開始同時瘋狂掃射,i即是靶子
複製代碼

3.4:i++發生了什麼?

1)內存中讀取i的值,2)i=i+1,3)將結果寫回內存
i=9時,若線程2已經在第三步了,但還沒寫入內存。這時線程1進入,讀出i的值還是9,
從而致使這次結束兩個結果都是10,這就是爲何達不到200000的緣由
這就至關於兩個神槍手同時開槍,靶子未及時反應而致使兩顆同彈併發

i++發生了什麼.png

不一樣步會出現什麼情況.png

4.怎麼解決呢?

先看問題出在哪,是兩我的同時開槍對一個靶子
一我的是不能在同一時刻發出兩法子彈的,so,方法1:
準備兩個靶子,各自統計(像每一個足球運動員一個足球同樣,10000我的怎麼辦,然並卵)
方法2:不容許兩我的同時開槍,這即是synchronized
神槍手1在掃射時,神射手2的槍自動鎖死,若是100條線程也是相似,某一刻只能一人開槍ide

public class WhatIsWrong implements Runnable {
    static WhatIsWrong instance = new WhatIsWrong();
    static int i = 0;

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();

        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);//200000
    }

    @Override
    public synchronized void run() {//只需輕輕加一個synchronized便可
        for (int j = 0; j < 100000; j++) {
            i++;
        }
    }
}
複製代碼

2、同步鎖

幾種鎖.png

0.測試代碼(此時還未同步)

先看一下幹了什麼事:線程建立不說了,run方法中:
打印信息-->當前線程睡三秒-->打印運行結束(以下圖)
根據時間線能夠看出來打印結果(能夠看出兩我的一塊兒睡了,這還得了...)

非同步分析.png

public class SynObj_Block implements Runnable {
    static SynObj_Block instance = new SynObj_Block();

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        System.out.println("對象鎖,代碼塊形式--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("運行結束,name:" + Thread.currentThread().getName());
    }
}
複製代碼

打印結果:

對象鎖,代碼塊形式--name:Thread-1
對象鎖,代碼塊形式--name:Thread-0
運行結束,name:Thread-0
運行結束,name:Thread-1
All Finished
複製代碼

1.對象鎖之同步代碼塊鎖

上面說兩個線程一塊兒睡了,線程1先睡,線程2進來也睡了,能忍嗎?不能忍!
快把哥的四十米大刀,不對,是大鎖拿來,在我睡覺前先把門鎖上
線程1進來睡,而後把門鎖上,線程2就進不來,只能等線程1把鎖打開

同步代碼塊.png


1.1:同步代碼塊的添加

其餘代碼不變,就不貼了

@Override
public void run() {
    synchronized (this) {
        System.out.println("對象鎖,代碼塊形式--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("運行結束,name:" + Thread.currentThread().getName());
    }
}
複製代碼

1.2:運行結果

可見線程1睡完,線程2才能進來睡

對象鎖,代碼塊形式--name:Thread-0
運行結束,name:Thread-0
對象鎖,代碼塊形式--name:Thread-1
運行結束,name:Thread-1
All Finished
複製代碼

1.3:鎖對象

等等,這this是什麼鬼?--有點基礎的都知道是當前類對象

System.out.println(this);// top.toly.併發.SynObj_Block@77c89a74

同步代碼塊synchronized()接收一個對象,該對象可任意指定:
Object lock = new Object();
synchronized (lock) {//TODO}  
新建一個對象也能夠
複製代碼

1.4:多把鎖

也會你會說:既然隨便一個對象均可以當作鎖對象,Java本身給內置個唄
還傳個參數,累不累人。等等,存在即合理,且看下面...
想一下若是一個房間兩張牀,你上來把門鎖了,豈不是不合理?
那該怎麼辦?兩扇門,兩把不一樣的鎖唄(就像兩我的合租一間大房子同樣)
你能夠根據圖中時間線好好想一想(畫個圖也不是那麼容易的...且看且珍惜)

兩把鎖.png

/**
 * 做者:張風捷特烈
 * 時間:2018/12/28 0028:19:16
 * 郵箱:1981462002@qq.com
 * 說明:對象鎖--代碼塊鎖
 */
public class SynObj_Block implements Runnable {
    static SynObj_Block instance = new SynObj_Block();
    Object lock1 = new Object();//第一把鎖
    Object lock2 = new Object();//第二把鎖
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();


        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        synchronized (lock1) {
            System.out.println("lock1開始--name:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("lock1結束,name:" + Thread.currentThread().getName());
        }
        synchronized (lock2) {
            System.out.println("lock2開始,代碼塊形式--name:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("lock2結束,name:" + Thread.currentThread().getName());
        }
    }
}
複製代碼
對象鎖lock1,代碼塊形式--name:Thread-0
lock1睡醒了,name:Thread-0
對象鎖lock1,代碼塊形式--name:Thread-1
對象鎖lock2,代碼塊形式--name:Thread-0
lock1睡醒了,name:Thread-1
lock2睡醒了,name:Thread-0
對象鎖lock2,代碼塊形式--name:Thread-1
lock2睡醒了,name:Thread-1
All Finished
複製代碼

有什麼好處?兩人合租房有什麼好處,多把鎖就有什麼好處。
可看出既完成任務,又減小了2秒,這也就兩個線程而已
若是百萬級的線程數,哪怕微小的效率提高都是有價值的


2.對象鎖之普通方法鎖

正如1.4所想:我就是想簡單的加個鎖,每次同步代碼塊還有傳個對象,挺煩的
因此有一個叫方法鎖,什麼對象每一個類都有?答案:this,方法鎖的對象默認是this

2.1:使用
/**
 * 做者:張風捷特烈
 * 時間:2018/12/28 0028:19:16
 * 郵箱:1981462002@qq.com
 * 說明:對象鎖--普通方法鎖
 */
public class SynObj_Method implements Runnable {
    static SynObj_Method instance = new SynObj_Method();

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        sleep3ms();
    }

    public synchronized void sleep3ms() {
        System.out.println("方法鎖測試--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("結束,name:" + Thread.currentThread().getName());
    }
}
複製代碼

2.2:打印結果

和同步代碼塊一致

方法鎖測試--name:Thread-0
結束,name:Thread-0
方法鎖測試--name:Thread-1
結束,name:Thread-1
All Finished
複製代碼

2.3:如何證實方法鎖的鎖對象是this

你說this就this?何以見得?

@Override
public void run() {
    sleep3ms();
    synchronized (this){
        System.out.println("測試開始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("測試結束,name:" + Thread.currentThread().getName());
    }
}

public synchronized void sleep3ms() {
    System.out.println("方法鎖測試--name:" + Thread.currentThread().getName());
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("結束,name:" + Thread.currentThread().getName());
}
複製代碼
方法鎖開始--name:Thread-0
方法鎖結束,name:Thread-0
同步代碼塊測試開始--name:Thread-0
同步代碼塊測試結束,name:Thread-0
方法鎖開始--name:Thread-1
方法鎖結束,name:Thread-1
同步代碼塊測試開始--name:Thread-1
同步代碼塊測試結束,name:Thread-1
All Finished
複製代碼

加上this同步代碼塊後:可見開始與結束兩兩配對
說明方法鎖和同步代碼塊的this鎖是一把鎖,也就是隻有一扇門,必須一個一個睡


2.4:反證
@Override
public void run() {
    sleep3ms();
    synchronized (""){
        System.out.println("測試開始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("測試結束,name:" + Thread.currentThread().getName());
    }
}

public synchronized void sleep3ms() {
    System.out.println("方法鎖測試--name:" + Thread.currentThread().getName());
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("結束,name:" + Thread.currentThread().getName());
}
複製代碼
方法鎖開始--name:Thread-0
方法鎖結束,name:Thread-0
方法鎖開始--name:Thread-1
同步代碼塊測試開始--name:Thread-0
方法鎖結束,name:Thread-1
同步代碼塊測試結束,name:Thread-0
同步代碼塊測試開始--name:Thread-1
同步代碼塊測試結束,name:Thread-1
All Finished
複製代碼

若是鎖不是this,這裏簡單點用"",可見Thread-0的結束後
Thread-1的方法鎖開始和Thread-0的同步代碼塊測試開始是同時打印出來的
說明有兩扇門,那兩把鎖不是同一把,也反向代表,非this會產生兩把鎖
綜上正反兩面,咱們能夠感覺到方法鎖的鎖對象是this


3.類鎖之靜態方法鎖(static方法+synchronized)

說是類鎖,實質上是使用了Class對象當作鎖,非要較真的話,你能夠把他看做對象鎖
Class對象有什麼特色:一個類能夠有多個對象,但僅有一個Class對象
這就能夠致使:類鎖只能在同一時刻被一個對象擁有


3.1.static方法+synchronized

普通方法+synchronized可是兩個不一樣的Runnable對象線程

public class Syn_Static_Method implements Runnable {
    static Syn_Static_Method instance1 = new Syn_Static_Method();
    static Syn_Static_Method instance2 = new Syn_Static_Method();
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance1);
        Thread thread_2 = new Thread(instance2);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }
    @Override
    public void run() {
        sleep3ms();
    }
    public synchronized void sleep3ms() {
        System.out.println("靜態方法鎖開始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("靜態方法鎖開始,name:" + Thread.currentThread().getName());
    }
}
複製代碼

好吧,用腳趾頭想一想也知道互不影響
這至關於兩我的有兩個家,各自進各自的家睡覺天經地義
你加synchronized鎖你家的門管我什麼事,因此synchronized這時並沒用處

靜態方法鎖開始--name:Thread-1
靜態方法鎖開始--name:Thread-0
靜態方法鎖開始,name:Thread-0
靜態方法鎖開始,name:Thread-1
All Finished
複製代碼

咱們都知道static關鍵字修飾的方法、變量,是能夠令於類名(Class對象)的
也就是不須要對象即可以運行,由static修飾的方法是不能用this對象的
這就是爲何加一個static,鎖就不一樣了的緣由,至於鎖是什麼,除了它的老大還有人選嗎?

/**
 * 做者:張風捷特烈
 * 時間:2018/12/28 0028:19:16
 * 郵箱:1981462002@qq.com
 * 說明:對象鎖--靜態方法鎖
 */
public class Syn_Static_Method implements Runnable {
    //同上...略
    public static synchronized void sleep3ms() {//我就輕輕加個static
       //同上...略
    }
}
複製代碼
靜態方法鎖開始--name:Thread-0
靜態方法鎖開始,name:Thread-0
靜態方法鎖開始--name:Thread-1
靜態方法鎖開始,name:Thread-1
All Finished
複製代碼

符合預期:這樣就將一個類給鎖起來了,只要是這個類的對象
都會生效,這也是它的優點,也是static的本意:靜態,具備全局控制力


4.類鎖之Class對象鎖

至關於把static+synchronized拆出來

/**
 * 做者:張風捷特烈
 * 時間:2018/12/28 0028:19:16
 * 郵箱:1981462002@qq.com
 * 說明:對象鎖--class鎖
 */
public class Syn_Class implements Runnable {
    static Syn_Class instance1 = new Syn_Class();
    static Syn_Class instance2 = new Syn_Class();

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance1);
        Thread thread_2 = new Thread(instance2);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        sleep3ms();
    }

    public void sleep3ms() {
        synchronized (Syn_Class.class) {
            System.out.println("class鎖開始--name:" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("class鎖開始,name:" + Thread.currentThread().getName());
        }
    }
}
複製代碼

class鎖開始--name:Thread-0
class鎖開始,name:Thread-0
class鎖開始--name:Thread-1
class鎖開始,name:Thread-1
All Finished
複製代碼

5.如今回頭來看

synchronized:同步的

官宣:
同步方法支持一種簡單的策略來[防止線程干擾]和[內存一致性錯誤]:
若是一個對象變量對多個線程可見,則對它的全部讀寫都是經過同步方法完成的

民宣:
保證同一時刻最多隻一個線程執行該段代碼,來保證併發安全
複製代碼

3、多線程訪問方法的一些狀況

感受有點...麻煩

1.兩個線程訪問一個對象的普通同步方法
2.兩個線程訪問兩個對象的普通同步方法
3.兩個線程訪問靜態同步方法
4.兩個線程分別訪問普通同步方法和非同步方法
5.兩個線程分別訪問一個對象的不一樣普通同步方法
6.兩個線程分別訪問靜態同步和非靜態同步方法
方法拋出異常後,會釋放鎖
複製代碼

1.兩個線程訪問一個對象的普通同步方法

二-->2中的例子:線程1,2訪問一個對象instance的同步方法:sleep3ms
同一個對象,須要等待鎖的釋放,才能進入普通同步方法


2.兩個線程訪問兩個對象的普通同步方法

二-->3-->3.1中第一個小例子(用腳趾頭想的那個)
同一類的兩個不一樣對象的普通同步方法,對於兩個線程而言,同步是無用的


3.兩個線程訪問靜態同步方法

二-->3-->3.1第二個小例子,輕輕加了個static
因爲靜態同步方法的鎖是class,鎖對該類的全部對象都有效


4.兩個線程分別訪問普通同步方法和非同步方法

線程2的方法沒加鎖(非同步方法),就進來睡了唄,也沒什麼特別的
注意兩頭幾乎同時執行,測試了幾回,兩頭的前後順序不定

兩個線程分別訪問普通同步方法和非同步方法.png

/**
 * 做者:張風捷特烈
 * 時間:2018/12/29 0029:11:31
 * 郵箱:1981462002@qq.com
 * 說明:兩個線程分別訪問普通同步方法和非同步方法
 */
public class SynOrNot implements Runnable {
    static SynOrNot instance = new SynOrNot();

    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")) {
            sleep3msSync();
        } else {
            sleep3msCommon();
        }
    }

    public void sleep3msCommon() {
        System.out.println("非同步方法開始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("非同步方法結束,name:" + Thread.currentThread().getName());
    }

    public synchronized void sleep3msSync() {
        System.out.println("同步方法開始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("同步方法結束,name:" + Thread.currentThread().getName());
    }
}
複製代碼
同步方法開始--name:Thread-0
非同步方法開始--name:Thread-1
同步方法結束,name:Thread-0
非同步方法結束,name:Thread-1
All Finished
複製代碼

5.兩個線程分別訪問一個對象的不一樣普通同步方法

因爲普通同步方法是this鎖,因此對不一樣普通同步方法鎖是一致的,都生效

兩個線程分別訪問一個對象的不一樣普通同步方法.png

/**
 * 做者:張風捷特烈
 * 時間:2018/12/29 0029:11:31
 * 郵箱:1981462002@qq.com
 * 說明:兩個線程分別訪問一個對象的不一樣普通同步方法
 */
public class SynOfTwo implements Runnable {
    static SynOfTwo instance = new SynOfTwo();
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance);
        Thread thread_2 = new Thread(instance);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")) {
            sleep3msSync1();
        } else {
            sleep3msSync2();
        }
    }

    public synchronized void sleep3msSync2() {
        System.out.println("sleep3msSync2方法開始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sleep3msSync2結束,name:" + Thread.currentThread().getName());
    }

    public synchronized void sleep3msSync1() {
        System.out.println("sleep3msSync1開始--name:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sleep3msSync1結束,name:" + Thread.currentThread().getName());
    }
}
複製代碼
sleep3msSync1開始--name:Thread-0
sleep3msSync1結束,name:Thread-0
sleep3msSync2方法開始--name:Thread-1
sleep3msSync2結束,name:Thread-1
All Finished
複製代碼

6.兩個線程分別訪問靜態同步和普通同步方法

不測試都知道:一個是class鎖,一個是this鎖,鎖不一樣,不生效
在第5個的基礎上加上static關鍵字,其他不變,結果不出所料

兩個線程分別訪問靜態同步和普通同步方法.png

public static synchronized void sleep3msSync2() {
複製代碼
sleep3msSync1開始--name:Thread-0
sleep3msSync2方法開始--name:Thread-1
sleep3msSync2結束,name:Thread-1
sleep3msSync1結束,name:Thread-0
All Finished
複製代碼

7.拋出異常後,釋放鎖

能夠看出線程1拋異常後,線程2是能夠正常運行的(說明線程1的鎖已經被釋放)
就像線程1在睡覺,睡着睡着仙逝了,房東(JVM)會把它擡走,把鎖給下一我的,繼續睡...
在第5個的代碼上稍微修改:int a=1/0;//異常

線程1出現異常.png

public synchronized void sleep3msSync1() {
    System.out.println("sleep3msSync1開始--name:" + Thread.currentThread().getName());
    try {
        Thread.sleep(3000);
        int a=1/0;//異常
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("sleep3msSync1結束,name:" + Thread.currentThread().getName());
}
複製代碼
sleep3msSync1開始--name:Thread-0
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
sleep3msSync2方法開始--name:Thread-1
	at top.toly.併發.SynOfError.sleep3msSync1(SynOfError.java:54)
	at top.toly.併發.SynOfError.run(SynOfError.java:33)
	at java.base/java.lang.Thread.run(Thread.java:844)
sleep3msSync2結束,name:Thread-1
All Finished
複製代碼

一把鎖只能由一個線程獲取,沒拿到鎖的線程必須等待
不一樣的鎖之間互不影響(至關於進不一樣的門,互不干擾,無需等待)
不管正常執行仍是拋出異常,都會釋放鎖


八、synchronized的性質
可重入:同一線程外層函數獲取鎖以後,內層函數能夠直接再次獲取該鎖
好處:避免死鎖,提升封裝性
粒度:線程範圍  
即synchronized修飾的同步方法內部`並不是只能`調用同步方法
複製代碼
不可中斷:好比我線程1要小睡個十萬年,那線程2就要在門等上十萬年(想走都不行)。
複製代碼

4、Java內存模型(JMM--Java Memory Model)

1.Java內存模型的概念

描述Java程序中各變量(線程共享變量)的訪問規則,
即在JVM中將變量存儲到內存和從內存中讀取變量的底層細節

1.全部的變量都存儲在主內存中,
2.每條線程都有本身獨立的工做內存。其保存該線程用到的變量副本(主內存變量拷貝)。

規定:
[1]線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存。
[2]線程沒法直接訪問非己方工做內存中的變量,線程間變量值的傳遞須要間接經過主內存。
複製代碼

JMM--java內存模型.png


2.如何:線程1的修改被線程2看到

1.工做內存1操做共享變量a後刷新到主內存
2.而後線程2從主內存中讀取共享變量a值並拷貝到本身的工做內存

共享變量可見性.png


三、synchronized實現可見性

鎖定的線程1所作的任何修改都要在釋放鎖以前從工做內存刷新到主內存
線程2拿到鎖時從主內存中拷貝須要的變量到本身的工做內存(從而實現共享變量的可見)


四、缺陷:
效率低:
鎖的釋放狀況少(只能自動釋放,或異常)
不能中斷等待鎖的線程

不靈活:
加鎖和釋放鎖的時機單一,每一個鎖只有單一的條件
沒法知道釋放成功獲取鎖
複製代碼

5.注意點
鎖對象不能爲空,做用域不宜過大,避免死鎖
|---鎖對象的信息是放在對象頭中,因此不能爲空
|---做用域過大,致使串行執行的代碼變多,效率降低

Lock仍是synchronized
|---儘可能使用併發包裏的原子類
|---synchronized能完成的儘可能不去Lock
|---確實須要中斷等待、靈活開解鎖或Condition可使用Lock鎖

多線程訪問同步方法的幾種狀況
複製代碼
死鎖簡單演示
/**
 * 做者:張風捷特烈
 * 時間:2018/12/29 0029:11:31
 * 郵箱:1981462002@qq.com
 * 說明:死鎖簡單演示
 */
public class SynKill implements Runnable {
    static SynKill instance1 = new SynKill();
    static SynKill instance2 = new SynKill();
    public static void main(String[] args) {
        Thread thread_1 = new Thread(instance1);
        Thread thread_2 = new Thread(instance1);
        thread_1.start();
        thread_2.start();
        try {
            thread_1.join();
            thread_2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("All Finished");
    }
    @Override
    public void run() {
        sleep3msSync1();
        sleep3msSync2();
    }

    public void sleep3msSync2() {
        synchronized (instance1) {
            System.out.println("sleep3msSync2方法開始--name:" + Thread.currentThread().getName());
            synchronized (instance2) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("sleep3msSync2結束,name:" + Thread.currentThread().getName());
       }
    }

    public static synchronized void sleep3msSync1() {
        synchronized (instance2) {
            System.out.println("sleep3msSync1方法開始--name:" + Thread.currentThread().getName());
            synchronized (instance1) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("sleep3msSync1結束,name:" + Thread.currentThread().getName());
        }
    }
}
複製代碼

死鎖.png


6、synchronized原理簡述

1.定義一個類,其中用一個同步方法
public class Decode {

    private Object obj = new Object();

    public void say(Thread thread) {
        synchronized (obj){

        }
    }

}
複製代碼

2.反編譯(含同步方法的類):
I:\Java\Base\Thinking\src\top\toly\併發>javac -encoding utf-8 Decode.java

I:\Java\Base\Thinking\src\top\toly\併發>javap -verbose Decode.class
Classfile /I:/Java/Base/Thinking/src/top/toly/併發/Decode.class
  Last modified 2018年12月29日; size 465 bytes
  MD5 checksum 732654b709aafd523b08c943dcb1f235
  Compiled from "Decode.java"
public class top.toly.併發.Decode
  minor version: 0
  major version: 54
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #4 // top/toly/併發/Decode
  super_class: #2 // java/lang/Object
  interfaces: 0, fields: 1, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref #2.#18 // java/lang/Object."<init>":()V
   #2 = Class #19 // java/lang/Object
   #3 = Fieldref #4.#20 // top/toly/併發/Decode.obj:Ljava/lang/Object;
   #4 = Class #21 // top/toly/併發/Decode
   #5 = Utf8 obj
   #6 = Utf8 Ljava/lang/Object;
   #7 = Utf8 <init>
   #8 = Utf8 ()V
   #9 = Utf8 Code
  #10 = Utf8 LineNumberTable
  #11 = Utf8 say
  #12 = Utf8 (Ljava/lang/Thread;)V
  #13 = Utf8 StackMapTable
  #14 = Class #22 // java/lang/Thread
  #15 = Class #23 // java/lang/Throwable
  #16 = Utf8 SourceFile
  #17 = Utf8 Decode.java
  #18 = NameAndType #7:#8 // "<init>":()V
  #19 = Utf8 java/lang/Object
  #20 = NameAndType #5:#6 // obj:Ljava/lang/Object;
  #21 = Utf8 top/toly/併發/Decode
  #22 = Utf8 java/lang/Thread
  #23 = Utf8 java/lang/Throwable
{
  public top.toly.併發.Decode();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1 // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #2 // class java/lang/Object
         8: dup
         9: invokespecial #1 // Method java/lang/Object."<init>":()V
        12: putfield      #3 // Field obj:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 9: 0
        line 11: 4

  public void say(java.lang.Thread);
    descriptor: (Ljava/lang/Thread;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
         0: aload_0
         1: getfield      #3 // Field obj:Ljava/lang/Object;
         4: dup
         5: astore_2
         6: monitorenter  <---------------monitorenter
         7: aload_2
         8: monitorexit   <---------------monitorexit
         9: goto          17
        12: astore_3
        13: aload_2
        14: monitorexit  <---------------monitorexit
        15: aload_3
        16: athrow
        17: return
      Exception table:
         from    to  target type
             7     9    12   any
            12    15    12   any
      LineNumberTable:
        line 14: 0
        line 16: 7
        line 17: 17
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class top/toly/併發/Decode, class java/lang/Thread, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "Decode.java"

複製代碼

3.若是將同步代碼塊去掉,再反編譯
I:\Java\Base\Thinking\src\top\toly\併發>javap -verbose Decode.class
Classfile /I:/Java/Base/Thinking/src/top/toly/併發/Decode.class
  Last modified 2018年12月29日; size 331 bytes
  MD5 checksum 7963d00f1f781bc47a9700c548692617
  Compiled from "Decode.java"
public class top.toly.併發.Decode
  minor version: 0
  major version: 54
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #4 // top/toly/併發/Decode
  super_class: #2 // java/lang/Object
  interfaces: 0, fields: 1, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref #2.#15 // java/lang/Object."<init>":()V
   #2 = Class #16 // java/lang/Object
   #3 = Fieldref #4.#17 // top/toly/併發/Decode.obj:Ljava/lang/Object;
   #4 = Class #18 // top/toly/併發/Decode
   #5 = Utf8 obj
   #6 = Utf8 Ljava/lang/Object;
   #7 = Utf8 <init>
   #8 = Utf8 ()V
   #9 = Utf8 Code
  #10 = Utf8 LineNumberTable
  #11 = Utf8 say
  #12 = Utf8 (Ljava/lang/Thread;)V
  #13 = Utf8 SourceFile
  #14 = Utf8 Decode.java
  #15 = NameAndType #7:#8 // "<init>":()V
  #16 = Utf8 java/lang/Object
  #17 = NameAndType #5:#6 // obj:Ljava/lang/Object;
  #18 = Utf8 top/toly/併發/Decode
{
  public top.toly.併發.Decode();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1 // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #2 // class java/lang/Object
         8: dup
         9: invokespecial #1 // Method java/lang/Object."<init>":()V
        12: putfield      #3 // Field obj:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 9: 0
        line 11: 4

  public void say(java.lang.Thread);
    descriptor: (Ljava/lang/Thread;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 14: 0
}
SourceFile: "Decode.java"

複製代碼

兩次的對比能夠看出:obj對象上的東西有點不同
加了synchronized代碼塊的,obj對象頭會有monitorentermonitorexit
注意是加鎖時使用的對象obj的對象頭


4.monitorentermonitorexit
monitorenter次數爲0時:若線程1進入,monitorenter次數+1,線程1成爲該Monitor的全部者
若此時線程2進入,因爲Monitor的全部者非線程2,線程2只能等待,直到monitorenter次數爲0

若線程1進入同步方法後,又調用了一次其餘方法,則monitorenter次數+1,方法退出時-1(可重入)
當monitorenter次數爲0,說明:線程1的該同步方法執行完畢,將工做內存刷新到主內存,並釋放鎖  
這時monitorenter次數爲0,線程2容許進入,monitorenter次數+1,線程2成爲該Monitor的全部者
複製代碼

更深的東西之後慢慢來吧,先了解個線程同步的大概,併發的內功也不是一朝一夕能成的


後記:捷文規範

1.本文成長記錄及勘誤表
項目源碼 日期 備註
V0.1 2018-12-29 燃燒吧!個人併發之魂--synchronized
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
個人github 個人簡書 個人掘金 我的網站
3.聲明

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持


icon_wx_200.png
相關文章
相關標籤/搜索