不會(huì)吧盒延,你連Java 多線程線程安全都還沒搞明白缩擂,難怪你面試總不過

什么是線程安全?

當(dāng)一個(gè)線程在同一時(shí)刻共享同一個(gè)全局變量或靜態(tài)變量時(shí)添寺,可能會(huì)受到其他線程的干擾胯盯,導(dǎo)致數(shù)據(jù)有問題,這種現(xiàn)象就叫線程安全問題计露。

為什么有線程安全問題博脑?

當(dāng)多個(gè)線程同時(shí)共享憎乙,同一個(gè)全局變量或靜態(tài)變量,做寫的操作時(shí)叉趣,可能會(huì)發(fā)生數(shù)據(jù)沖突問題泞边,也就是線程安全問題,但是做讀操作時(shí)不會(huì)發(fā)生數(shù)據(jù)沖突問題疗杉。

線程安全解決辦法阵谚?

1、如何解決多線程之間線程安全問題烟具?

答:使用多線程之間同步synchronized或使用鎖(lock)

2椭蹄、為什么使用線程同步或使用鎖能解決線程安全問題呢?

答:將可能會(huì)發(fā)生數(shù)據(jù)沖突問題(線程不安全問題)净赴,只能讓當(dāng)前一個(gè)線程進(jìn)行執(zhí)行。代碼執(zhí)行完成后釋放鎖罩润,讓后才能讓其他線程進(jìn)行執(zhí)行玖翅。這樣的話就可以解決線程不安全問題。

3割以、什么是多線程之間同步金度?

答:當(dāng)多個(gè)線程共享同一個(gè)資源,不會(huì)受到其他線程的干擾。

同步代碼塊

1严沥、什么是同步代碼塊猜极?

答:就是將可能會(huì)發(fā)生線程安全問題的代碼,用synchronized給包括起來消玄。

synchronized(同一個(gè)數(shù)據(jù)) {
 // 可能會(huì)發(fā)生線程沖突問題
}

synchronized(對象) {//這個(gè)對象可以為任意對象 
    // 需要被同步的代碼 
} 

對象如同鎖跟伏,持有鎖的線程可以在同步中執(zhí)行。
沒持有鎖的線程即使獲取CPU的執(zhí)行權(quán)翩瓜,也進(jìn)不去受扳。

同步的前提:

必須有兩個(gè)或者兩個(gè)以上的線程
必須是多個(gè)線程使用同一個(gè)鎖
必須保證同步中只能有一個(gè)線程在運(yùn)行
同步的好處:
解決了多線程的安全問題

同步的弊端:
多個(gè)線程需要判斷鎖,較為消耗資源兔跌、搶鎖的資源勘高。

同步函數(shù)

1、什么是同步函數(shù)坟桅?

答:在方法上修飾 synchronized 稱為同步函數(shù)

public synchronized void sale() {
    if (trainCount > 0) { 
        try {
            Thread.sleep(40);
        } catch (Exception e) {
            
        }
        System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
        trainCount--;
        }
    }

同步函數(shù)使用的是 this 鎖

證明方式:一個(gè)線程使用同步代碼塊(this明鎖)华望,另一個(gè)線程使用同步函數(shù)。如果兩個(gè)線程搶票不能實(shí)現(xiàn)同步仅乓,那么會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)誤赖舟。


package cn.icloudit;

class ThreadTrain2 implements Runnable {
    private int count = 100;
    public boolean flag = true;
    private static Object oj = new Object();

    @Override
    public void run() {
        if (flag) {
            while (count > 0) {
                synchronized (this) {
                    if (count > 0) {
                        try {
                            Thread.sleep(50);
                        } catch (Exception e) {
                            // TODO: handle exception
                        }
                        System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
                        count--;
                    }
                }

            }

        } else {
            while (count > 0) {
                sale();
            }
        }

    }

    public synchronized void sale() {
        // 前提 多線程進(jìn)行使用、多個(gè)線程只能拿到一把鎖夸楣。
        // 保證只能讓一個(gè)線程 在執(zhí)行 缺點(diǎn)效率降低
        // synchronized (oj) {
        if (count > 0) {
            try {
                Thread.sleep(50);
            } catch (Exception e) {
                // TODO: handle exception
            }
            System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
            count--;
        }
        // }
    }
}

public class ThreadDemo2 {
    public static void main(String[] args) throws InterruptedException {
        ThreadTrain2 threadTrain1 = new ThreadTrain2();
        Thread t1 = new Thread(threadTrain1, "①號(hào)窗口");
        Thread t2 = new Thread(threadTrain1, "②號(hào)窗口");
        t1.start();
        Thread.sleep(40);
        threadTrain1.flag = false;
        t2.start();
    }
}

靜態(tài)同步函數(shù)

1建蹄、什么是靜態(tài)同步函數(shù)碌更?

答:方法上加上 static 關(guān)鍵字,使用 synchronized 關(guān)鍵字修飾洞慎,或者使用類 .class 文件痛单。

靜態(tài)的同步函數(shù)使用的鎖是 該函數(shù)所屬字節(jié)碼文件對象
可以用 getClass方法獲取,也可以用當(dāng)前 類名.class 表示劲腿。

synchronized (ThreadTrain.class) {
    System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
    trainCount--;
    try {
        Thread.sleep(100);
    } catch (Exception e) {
    }   
}

總結(jié):
synchronized 修飾方法使用鎖是當(dāng)前 this 鎖旭绒。
synchronized 修飾靜態(tài)方法使用鎖是當(dāng)前類的 字節(jié)碼文件。

多線程死鎖

什么是多線程死鎖焦人?

答:同步中嵌套同步挥吵,導(dǎo)致鎖無法釋放

package cn.icloudit;

class ThreadTrain6 implements Runnable {
    // 這是貨票總票數(shù),多個(gè)線程會(huì)同時(shí)共享資源
    private int trainCount = 100;
    public boolean flag = true;
    private Object mutex = new Object();

    @Override
    public void run() {
        if (flag) {
            while (true) {
                synchronized (mutex) {
                    // 鎖(同步代碼塊)在什么時(shí)候釋放? 代碼執(zhí)行完花椭, 自動(dòng)釋放鎖.
                    // 如果flag為true 先拿到 obj鎖,在拿到this 鎖忽匈、 才能執(zhí)行。
                    // 如果flag為false先拿到this,在拿到obj鎖矿辽,才能執(zhí)行丹允。
                    // 死鎖解決辦法:不要在同步中嵌套同步。
                    sale();
                }
            }
        } else {
            while (true) {
                sale();
            }
        }
    }
    
    public synchronized void sale() {
        synchronized (mutex) {
            if (trainCount > 0) {
                try {
                    Thread.sleep(40);
                } catch (Exception e) {

                }
                System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
                trainCount--;
            }
        }
    }
}

public class DeadlockThread {

    public static void main(String[] args) throws InterruptedException {
        ThreadTrain6 threadTrain = new ThreadTrain6(); // 定義 一個(gè)實(shí)例
        Thread thread1 = new Thread(threadTrain, "一號(hào)窗口");
        Thread thread2 = new Thread(threadTrain, "二號(hào)窗口");
        thread1.start();
        Thread.sleep(40);
        threadTrain.flag = false;
        thread2.start();
    }

}

多線程的三大特性

原子性

即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過程不會(huì)被任何因素打斷袋倔,要么就都不執(zhí)行雕蔽。

一個(gè)很經(jīng)典的例子就是銀行賬戶轉(zhuǎn)賬問題:
比如從賬戶A向賬戶B轉(zhuǎn)1000元,那么必然包括2個(gè)操作:從賬戶A減去1000元宾娜,往賬戶B加上1000元批狐。這2個(gè)操作必須要具備原子性才能保證不出現(xiàn)一些意外的問題。

我們操作數(shù)據(jù)也是如此前塔,比如i = i+1嚣艇;其中就包括,讀取i的值华弓,計(jì)算i髓废,寫入i。這行代碼在Java中是不具備原子性的该抒,則多線程運(yùn)行肯定會(huì)出問題慌洪,所以也需要我們使用同步和lock這些東西來確保這個(gè)特性了。

原子性其實(shí)就是保證數(shù)據(jù)一致凑保、線程安全一部分冈爹。

可見性

當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值欧引,其他線程能夠立即看得到修改的值频伤。

若兩個(gè)線程在不同的cpu,那么線程1改變了i的值還沒刷新到主存芝此,線程2又使用了i憋肖,那么這個(gè)i值肯定還是之前的因痛,線程1對變量的修改線程沒看到這就是可見性問題。

有序性

程序執(zhí)行的順序按照代碼的先后順序執(zhí)行岸更。

一般來說處理器為了提高程序運(yùn)行效率鸵膏,可能會(huì)對輸入代碼進(jìn)行優(yōu)化,它不保證程序中各個(gè)語句的執(zhí)行先后順序同代碼中的順序一致怎炊,但是它會(huì)保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的谭企。如下:
int a = 10; //語句1
int r = 2; //語句2
a = a + 3; //語句3
r = a*a; //語句4
則因?yàn)橹嘏判颍€可能執(zhí)行順序?yàn)?2-1-3-4评肆,1-3-2-4
但絕不可能 2-1-4-3债查,因?yàn)檫@打破了依賴關(guān)系。

顯然重排序?qū)尉€程運(yùn)行是不會(huì)有任何問題瓜挽,而多線程就不一定了盹廷,所以我們在多線程編程時(shí)就得考慮這個(gè)問題了。

Java內(nèi)存模型

共享內(nèi)存模型指的就是Java內(nèi)存模型(簡稱JMM)久橙,JMM決定一個(gè)線程對共享變量的寫入時(shí),能對另一個(gè)線程可見俄占。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(main memory)中剥汤,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本排惨。本地內(nèi)存是JMM的一個(gè)抽象概念吭敢,并不真實(shí)存在。它涵蓋了緩存暮芭,寫緩沖區(qū)鹿驼,寄存器以及其他的硬件和編譯器優(yōu)化。

總結(jié):什么是Java內(nèi)存模型:java內(nèi)存模型簡稱jmm辕宏,定義了一個(gè)線程對另一個(gè)線程可見畜晰。共享變量存放在主內(nèi)存中,每個(gè)線程都有自己的本地內(nèi)存瑞筐,當(dāng)多個(gè)線程同時(shí)訪問一個(gè)數(shù)據(jù)的時(shí)候凄鼻,可能本地內(nèi)存沒有及時(shí)刷新到主內(nèi)存,所以就會(huì)發(fā)生線程安全問題聚假。

Volatile 關(guān)鍵字

什么是 Volatile 關(guān)鍵字块蚌?
答:Volatile 關(guān)鍵字的作用是變量在多個(gè)線程之間可見。

class ThreadVolatileDemo extends Thread {
    public    boolean flag = true;
    @Override
    public void run() {
        System.out.println("開始執(zhí)行子線程....");
        while (flag) {
        }
        System.out.println("線程停止");
    }
    public void setRuning(boolean flag) {
        this.flag = flag;
    }

}

public class ThreadVolatile {
    public static void main(String[] args) throws InterruptedException {
        ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
        threadVolatileDemo.start();
        Thread.sleep(3000);
        threadVolatileDemo.setRuning(false);
        System.out.println("flag 已經(jīng)設(shè)置成false");
        Thread.sleep(1000);
        System.out.println(threadVolatileDemo.flag);

    }
}

已經(jīng)將結(jié)果設(shè)置為fasle為什么膘格?還一直在運(yùn)行呢峭范。

原因:線程之間是不可見的,讀取的是副本瘪贱,沒有及時(shí)讀取到主內(nèi)存結(jié)果纱控。

解決辦法:使用Volatile關(guān)鍵字將解決線程之間可見性, 強(qiáng)制線程每次讀取該值的時(shí)候都去“主內(nèi)存”中取值

Volatile非原子性

public class VolatileNoAtomic extends Thread {
    private static volatile int count;

    // private static AtomicInteger count = new AtomicInteger(0);
    private static void addCount() {
        for (int i = 0; i < 1000; i++) {
            count++;
            // count.incrementAndGet();
        }
        System.out.println(count);
    }

    public void run() {
        addCount();
    }

    public static void main(String[] args) {

        VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
        for (int i = 0; i < 10; i++) {
            arr[i] = new VolatileNoAtomic();
        }

        for (int i = 0; i < 10; i++) {
            arr[i].start();
        }
    }

}

結(jié)果發(fā)現(xiàn) 數(shù)據(jù)不同步辆毡,因?yàn)閂olatile不用具備原子性。

AtomicInteger原子類

AtomicInteger是一個(gè)提供原子操作的Integer類甜害,通過線程安全的方式操作加減舶掖。

public class VolatileNoAtomic extends Thread {
    static int count = 0;
    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            //等同于i++
            atomicInteger.incrementAndGet();
        }
        System.out.println(count);
    }

    public static void main(String[] args) {
        // 初始化10個(gè)線程
        VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10];
        for (int i = 0; i < 10; i++) {
            // 創(chuàng)建
            volatileNoAtomic[i] = new VolatileNoAtomic();
        }
        for (int i = 0; i < volatileNoAtomic.length; i++) {
            volatileNoAtomic[i].start();
        }
    }

}

volatile與synchronized區(qū)別

僅靠volatile不能保證線程的安全性。(原子性)

volatile輕量級唾那,只能修飾變量访锻。synchronized重量級,還可修飾方法闹获;
volatile只能保證數(shù)據(jù)的可見性期犬,不能用來同步,因?yàn)槎鄠€(gè)線程并發(fā)訪問volatile修飾的變量不會(huì)阻塞避诽。
synchronized 不僅保證可見性龟虎,而且還保證原子性,因?yàn)樯陈挥蝎@得了鎖的線程才能進(jìn)入臨界區(qū)鲤妥,從而保證臨界區(qū)中的所有語句都全部執(zhí)行。多個(gè)線程爭搶synchronized鎖對象時(shí)拱雏,會(huì)出現(xiàn)阻塞棉安。

最后

感謝你看到這里,看完有什么的不懂的可以在評論區(qū)問我铸抑,覺得文章對你有幫助的話記得給我點(diǎn)個(gè)贊贡耽,每天都會(huì)分享java相關(guān)技術(shù)文章或行業(yè)資訊,歡迎大家關(guān)注和轉(zhuǎn)發(fā)文章鹊汛!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒲赂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子刁憋,更是在濱河造成了極大的恐慌滥嘴,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件至耻,死亡現(xiàn)場離奇詭異若皱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)尘颓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門是尖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泥耀,你說我怎么就攤上這事饺汹。” “怎么了痰催?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵兜辞,是天一觀的道長迎瞧。 經(jīng)常有香客問我,道長逸吵,這世上最難降的妖魔是什么凶硅? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮扫皱,結(jié)果婚禮上足绅,老公的妹妹穿的比我還像新娘。我一直安慰自己韩脑,他們只是感情好氢妈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著段多,像睡著了一般首量。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上进苍,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天加缘,我揣著相機(jī)與錄音,去河邊找鬼觉啊。 笑死拣宏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的杠人。 我是一名探鬼主播勋乾,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼搜吧!你這毒婦竟也來了市俊?” 一聲冷哼從身側(cè)響起杨凑,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤滤奈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后撩满,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜒程,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屠缭。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡干毅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出让蕾,到底是詐尸還是另有隱情,我是刑警寧澤叫惊,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布帝洪,位于F島的核電站似舵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏葱峡。R本人自食惡果不足惜砚哗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砰奕。 院中可真熱鬧蛛芥,春花似錦、人聲如沸军援。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盖溺。三九已至漓糙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烘嘱,已是汗流浹背昆禽。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝇庭,地道東北人醉鳖。 一個(gè)月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像哮内,于是被迫代替她去往敵國和親盗棵。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355

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