關(guān)于線程安全問題乳规,這一篇應(yīng)該是全網(wǎng)講的最明白的了剿配!

線程安全與不安全

線程安全:當(dāng)多線程訪問時(shí)棒假,采用了加鎖的機(jī)制歇盼;即當(dāng)一個(gè)線程訪問該類的某一個(gè)數(shù)據(jù)時(shí)道宅,會(huì)對(duì)這個(gè)數(shù)據(jù)進(jìn)行保護(hù)萧豆,其他線程不能對(duì)其訪問臼朗,直到該線程讀取結(jié)束之后邻寿,其他線程才可以使用。防止出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)被污染的情況视哑。
線程不安全:多個(gè)線程同時(shí)操作某個(gè)數(shù)據(jù)绣否,出現(xiàn)數(shù)據(jù)不一致或者被污染的情況。

代碼示例:

package thread_5_10;

public class Demo26 {
    static  int  a = 0;
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100_0000; i++) {
                    a++;
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100_0000; i++) {
                    a--;
                }
            }
        });

        //開啟線程
        t1.start();
        t2.start();

        //等待線程完成
        //t1.join();
        //t2.join();
        while(t1.isAlive() || t2.isAlive()){

        }
        System.out.println(a);
    }
}


運(yùn)行結(jié)果:

493612

結(jié)果分析:



個(gè)人整理了一些資料挡毅,有需要的朋友可以直接點(diǎn)擊領(lǐng)取蒜撮。

Java基礎(chǔ)知識(shí)大全

22本Java架構(gòu)師核心書籍

從0到1Java學(xué)習(xí)路線和資料

[1000+道2021年最新面試題](https://jq.qq.com/?_wv=1027&k=6SfDAjTT

線程不安全的因素:

CPU是搶占式執(zhí)行的(搶占資源)
多個(gè)線程操作的是同一個(gè)變量
可見性
非原子性
編譯期優(yōu)化(指令重排)

volatile

volatile是指令關(guān)鍵字,作用是確保本指令不會(huì)因編譯期優(yōu)化而省略跪呈,且每次要求直接讀值段磨。可以解決內(nèi)存不可見和指令重排序的問題耗绿,但是不能解決原子性問題

解決線程不安全

有兩種加鎖方式:

synchronized(jvm層的解決方案)
Lock手動(dòng)鎖

synchronized

操作鎖的流程

嘗試獲取鎖a
使用鎖(這一步驟是具體的業(yè)務(wù)代碼)
釋放鎖
synchronized是JVM層面鎖的解決方案苹支,它幫我們實(shí)現(xiàn)了加鎖和釋放鎖的過程

代碼示例

package thread_5_10;

public class Demo31 {
    //循環(huán)的最大次數(shù)
    private final static int maxSize = 100_0000;

    //定義全局變量
    private static int number = 0;

    public static void main(String[] args) throws InterruptedException {

        //聲明鎖對(duì)象
        Object obj = new Object();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    //實(shí)現(xiàn)加鎖
                    synchronized (obj){
                        number++;
                    }

                }
            }
        });

        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    synchronized (obj){
                        number--;
                    }

                }
            }
        });

        t2.start();

        //等待兩個(gè)線程執(zhí)行完成
        t1.join();
        t2.join();

        System.out.println(number);
    }
}

運(yùn)行結(jié)果:

0

解析:


注意

synchronized實(shí)現(xiàn)分為:

操作系統(tǒng)層面,它是依靠互斥鎖mutex
針對(duì)JVM误阻,monitor實(shí)現(xiàn)
針對(duì)Java語(yǔ)言來說债蜜,是將鎖信息存放在對(duì)象頭中

三種使用場(chǎng)景

使用synchronized修飾代碼塊晴埂,(可以對(duì)任意對(duì)象加鎖)
使用synchronized修飾靜態(tài)方法(對(duì)當(dāng)前類進(jìn)行加鎖)
使用synchronized修飾普通方法(對(duì)當(dāng)前類實(shí)例進(jìn)行加鎖)
修飾靜態(tài)方法:

package thread_5_10;

public class Demo32 {

    private static int number = 0;
    private static final int maxSize = 100_0000;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                increment();
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                decrement();
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("最終結(jié)果為:"+number);
    }

    public synchronized static void increment(){
        for (int i = 0; i < maxSize; i++) {
            number++;
        }
    }

    public synchronized static void decrement(){
        for (int i = 0; i < maxSize; i++) {
            number--;
        }
    }
}


修飾實(shí)例方法:

package thread_5_10;

public class Demo33 {

    private static int number = 0;
    private static final int maxSize = 100_0000;

    public static void main(String[] args) throws InterruptedException {
        Demo33 demo = new Demo33();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                demo.increment();
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                demo.decrement();
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("最終結(jié)果:"+number);
    }

    public synchronized  void increment(){
        for (int i = 0; i < maxSize; i++) {
            number++;
        }
    }

    public synchronized  void decrement(){
        for (int i = 0; i < maxSize; i++) {
            number--;
        }
    }
}


Lock手動(dòng)鎖

代碼示例:

package thread_5_10;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo34 {

    private static int number = 0;
    private static final int maxSize = 100_0000;

    public static void main(String[] args) throws InterruptedException {

        //創(chuàng)建lock實(shí)例
        Lock lock = new ReentrantLock();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try{
                        number++;
                    }finally {
                        lock.unlock();
                    }
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try{
                        number--;
                    }finally {
                        lock.unlock();
                    }
                }
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("最終結(jié)果為-->   "+number);
    }
}


運(yùn)行結(jié)果:

最終結(jié)果為-->   0

注意事項(xiàng):

lock()一定要放在try外面

如果放在try里面,如果try里面出現(xiàn)異常寻定,還沒有加鎖成功就執(zhí)行finally里面的釋放鎖的代碼儒洛,就會(huì)出現(xiàn)異常
如果放在try里面,如果沒有鎖的情況下釋放鎖特姐,這個(gè)時(shí)候產(chǎn)生的異常就會(huì)把業(yè)務(wù)代碼里面的異常給吞噬掉晶丘,增加代碼調(diào)試的難度

公平鎖與非公平鎖

公平鎖:當(dāng)一個(gè)線程釋放鎖之后黍氮,需要主動(dòng)喚醒“需要得到鎖”的隊(duì)列來得到鎖
非公平鎖:當(dāng)一個(gè)線程釋放鎖之后唐含,另一個(gè)線程剛好執(zhí)行到獲取鎖的代碼就可以直接獲取鎖
java語(yǔ)言中,所有鎖的默認(rèn)實(shí)現(xiàn)方式都是非公平鎖

1.synchronized是非公平鎖
2.reentrantLock默認(rèn)是非公平鎖沫浆,但也可以顯示地聲明為公平鎖

顯示聲明公平鎖格式:


ReentrantLock源碼:


示例一:

package thread_5_10;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo36 {
    public static void main(String[] args) throws InterruptedException {

        Lock lock = new ReentrantLock(true);

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    lock.lock();
                    try{
                        System.out.println("線程1");
                    }finally {
                        lock.unlock();
                    }
                }
            }
        });



        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    lock.lock();
                    try{
                        System.out.println("線程2");
                    }finally {
                        lock.unlock();
                    }
                }
            }
        });

        Thread.sleep(1000);
        t1.start();
        t2.start();

    }
}


運(yùn)行結(jié)果:


示例二:

package test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class test08 {
    public static void main(String[] args) throws InterruptedException {

        Lock lock = new ReentrantLock(true);

        Runnable r = new Runnable() {
            @Override
            public void run() {
                for(char ch: "ABCD".toCharArray()){
                    lock.lock();
                    try{
                        System.out.print(ch);
                    }finally {
                        lock.unlock();
                    }
                }
            }
        };

        Thread.sleep(100);
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }
}


運(yùn)行結(jié)果:

AABBCCDD

兩種鎖區(qū)別

synchronized和lock的區(qū)別

關(guān)鍵字不同
synchronized自動(dòng)進(jìn)行加鎖和釋放鎖捷枯,而Lock需要手動(dòng)加鎖和釋放鎖
synchronized是JVM層面上的實(shí)現(xiàn),而Lock是Java層面鎖的實(shí)現(xiàn)
修飾范圍不同专执,synchronized可以修飾代碼塊淮捆,靜態(tài)方法,實(shí)例方法本股,而Lock只能修飾代碼塊
synchronized鎖的模式是非公平鎖攀痊,而lock鎖的模式是公平鎖和非公平鎖
Lock的靈活性更高

死鎖

死鎖定義

在兩個(gè)或兩個(gè)以上的線程運(yùn)行中,因?yàn)橘Y源搶占而造成線程一直等待的問題


當(dāng)線程1擁有資源并1且試圖獲取資源2和線程2擁有了資源2拄显,并且試圖獲取資源1的時(shí)候苟径,就發(fā)了死鎖

死鎖示例

package thread_5_11;

public class Demo36 {
    public static void main(String[] args) {
        //聲明加鎖的資源
        Object lock1 = new Object();
        Object lock2 = new Object();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //獲取線程名稱
                String threadName = Thread.currentThread().getName();

                //1.獲取資源1
                synchronized (lock1){
                    System.out.println(threadName+" 獲取到了lock1");
                    try {

                        //2.等待1ms,讓線程t1和線程t2都獲取到相應(yīng)的資源
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(threadName+" waiting lock2");

                    //3.獲取資源2
                    synchronized (lock2){
                        System.out.println(threadName+" 獲取到了lock2");
                    }
                }
            }
        },"t1");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                synchronized (lock2){
                    System.out.println(threadName+" 獲取到了lock2");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(threadName+" waiting lock1");
                    synchronized (lock1){
                        System.out.println(threadName+" 獲取到了lock1");
                    }
                }
            }
        },"t2");

        t2.start();
    }
}

運(yùn)行結(jié)果:


通過工具來查看死鎖:
(1)jdk–>bin–>jconsole.exe


(2)jdk–>bin–>jvisualvm.exe



(3)jdk–>bin–>jmc.exe


死鎖的4個(gè)必要條件

1.互斥條件:當(dāng)資源被一個(gè)線程擁有之后躬审,就不能被其他的線程擁有了
2.占有且等待:當(dāng)一個(gè)線程擁有了一個(gè)資源之后又試圖請(qǐng)求另一個(gè)資源
3.不可搶占:當(dāng)一個(gè)資源被一個(gè)線程被擁有之后棘街,如果不是這個(gè)線程主動(dòng)釋放此資源的情況下,其他線程不能擁有此資源
4.循環(huán)等待:兩個(gè)或兩個(gè)以上的線程在擁有了資源之后承边,試圖獲取對(duì)方資源的時(shí)候形成了一個(gè)環(huán)路

線程通訊

所謂的線程通訊就是在一個(gè)線程中的操作可以影響另一個(gè)線程遭殉,wait(休眠線程),notify(喚醒一個(gè)線程)博助,notifyall(喚醒所有線程)

wait方法

注意事項(xiàng):
1.wait方法在執(zhí)行之前必須先加鎖险污。也就是wait方法必須配合synchronized配合使用
2.wait和notify在配合synchronized使用時(shí),一定要使用同一把鎖


運(yùn)行結(jié)果:

wait之前
主線程喚醒t1
wait之后

多線程

package thread_5_13;

public class demo40 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //調(diào)用wait方法之前必須先加鎖
                synchronized (lock){
                    try {
                        System.out.println("t1 wait之前");
                        lock.wait();
                        System.out.println("t1 wait之后");

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        },"t1");


        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //調(diào)用wait方法之前必須先加鎖
                synchronized (lock){
                    try {
                        System.out.println("t2 wait之前");
                        lock.wait();
                        System.out.println("t2 wait之后");

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        },"t2");

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                //調(diào)用wait方法之前必須先加鎖
                synchronized (lock){
                    try {
                        System.out.println("t3 wait之前");
                        lock.wait();
                        System.out.println("t3 wait之后");

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        },"t3");

        t1.start();
        t2.start();
        t3.start();
        Thread.sleep(1000);
        System.out.println("主線程調(diào)用喚醒操作");

        //在主線程中喚醒
        synchronized (lock){
            lock.notify();
        }
    }
}


運(yùn)行結(jié)果:

t1 wait之前
t2 wait之前
t3 wait之前
主線程調(diào)用喚醒操作
t1 wait之后

注意事項(xiàng):

將lock.notify()修改為lock.notifyAll()富岳,則三個(gè)線程都能被喚醒
wait在不傳遞任何參數(shù)的情況下會(huì)進(jìn)入waiting狀態(tài)(參數(shù)為0也是waiting狀態(tài))蛔糯;當(dāng)wait里面有一個(gè)大于0的整數(shù)時(shí),它就會(huì)進(jìn)入timed_waiting狀態(tài)
關(guān)于wait和sleep釋放鎖的代碼:



wait在等待的時(shí)候可以釋放鎖城瞎,sleep在等待的時(shí)候不會(huì)釋放鎖

wait方法與sleep方法對(duì)比

相同點(diǎn):
(1)wait和sleep都可以使線程休眠
(2)wait和sleep在執(zhí)行的過程中都可以接收到終止線程執(zhí)行的通知

不同點(diǎn):
(1)wait必須synchronized一起使用渤闷,而sleep不用
(2)wait會(huì)釋放鎖,sleep不會(huì)釋放鎖
(3)wait是Object的方法脖镀,而sleep是Thread的方法
(4)默認(rèn)情況下飒箭,wait不傳遞參數(shù)或者參數(shù)為0的情況下狼电,它會(huì)進(jìn)入waiting狀態(tài),而sleep會(huì)進(jìn)入timed_waiting狀態(tài)
(5)使用wait可以主動(dòng)喚醒線程弦蹂,而使用sleep不能主動(dòng)喚醒線程

面試題

1.問:sleep(0)和wait(0)有什么區(qū)別
答:(1)sleep(0)表示過0毫秒后繼續(xù)執(zhí)行肩碟,而wait(0)會(huì)一直等待
(2)sleep(0)表示重新觸發(fā)一次CPU競(jìng)爭(zhēng)

2.為什么wait會(huì)釋放鎖,而sleep不會(huì)釋放鎖
答:sleep必須要傳遞一個(gè)最大等待時(shí)間的凸椿,也就是說sleep是可控的(對(duì)于時(shí)間層面來講)削祈,而wait是可以不傳遞時(shí)間,從設(shè)計(jì)層面來講脑漫,如果讓wait這個(gè)沒有超時(shí)等待時(shí)間的機(jī)制下釋放鎖的話髓抑,那么線程可能會(huì)一直阻塞,而sleep不會(huì)存在這個(gè)問題

3.為什么wait是Object的方法优幸,而sleep是Thread的方法
答:wait需要操作鎖吨拍,而鎖是對(duì)象級(jí)別(所有的鎖都在對(duì)象頭當(dāng)中),它不是線程級(jí)別网杆,一個(gè)線程可以有多把鎖羹饰,為了靈活起見,所有把wait放在Object當(dāng)中

4.解決wait/notify隨機(jī)喚醒的問題
答:可以使用LockSupport中的park碳却,unpark方法队秩,注意:locksupport雖然不會(huì)報(bào)interrupted的異常,但是可以監(jiān)聽到線程終止的指令

最后

都看到這里了昼浦,記得點(diǎn)個(gè)贊哦馍资!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市座柱,隨后出現(xiàn)的幾起案子迷帜,更是在濱河造成了極大的恐慌,老刑警劉巖色洞,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戏锹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡火诸,警方通過查閱死者的電腦和手機(jī)锦针,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來置蜀,“玉大人奈搜,你說我怎么就攤上這事《⒒纾” “怎么了馋吗?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)秋秤。 經(jīng)常有香客問我宏粤,道長(zhǎng)脚翘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任绍哎,我火速辦了婚禮来农,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘崇堰。我一直安慰自己沃于,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布海诲。 她就那樣靜靜地躺著繁莹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饿肺。 梳的紋絲不亂的頭發(fā)上蒋困,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音敬辣,去河邊找鬼。 笑死零院,一個(gè)胖子當(dāng)著我的面吹牛溉跃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播告抄,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼撰茎,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了打洼?” 一聲冷哼從身側(cè)響起龄糊,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎募疮,沒想到半個(gè)月后炫惩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阿浓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年他嚷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芭毙。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筋蓖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出退敦,到底是詐尸還是另有隱情粘咖,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布侈百,位于F島的核電站瓮下,受9級(jí)特大地震影響忠聚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唱捣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一两蟀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧震缭,春花似錦赂毯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至巡社,卻和暖如春膛堤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晌该。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工肥荔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朝群。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓燕耿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親姜胖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子誉帅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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