2020-01-31-Java多線程

線程生命周期

線程生命周期.jpg

NEW:創(chuàng)建了一個(gè)線程對(duì)象,但是還沒(méi)有調(diào)用start()方法慨飘。此時(shí)稱為初始狀態(tài)NEW
RUNNABLE:調(diào)用了start()方法后某残,進(jìn)入就緒狀態(tài),此時(shí)已經(jīng)進(jìn)入run()方法
BLOCKED:如果run()方法中有用synchronized包含的代碼塊蹈丸,則需要等其他線程釋放鎖對(duì)象才能進(jìn)入synchronized代碼塊成黄。
BLOCKED狀態(tài)是不能被interrupt打斷的呐芥,只有其他線程釋放鎖之后儡遮,才能回到RUNNABLE狀態(tài)柬脸。
WAITING:如果線程主動(dòng)調(diào)用wait()或join()方法,則進(jìn)入WAITING狀態(tài)囚似,
直到j(luò)oin()的線程返回闻伶;
或者wait()的對(duì)象執(zhí)行notify()或notifyAll()方法被喚醒滨攻;
或者其他線程調(diào)用interrupt()方法。
如果沒(méi)有其它線程喚醒蓝翰,WATING狀態(tài)會(huì)無(wú)限期等待下去光绕,形成死鎖。
TIMED_WAITING:如果線程主動(dòng)調(diào)用sleep(long millis)畜份,wait(long millis)诞帐,或join(long millis)方法,則進(jìn)入TIMED_WAITING狀態(tài)爆雹,
直到超時(shí)返回或join()的線程返回停蕉;
或者wait()的對(duì)象執(zhí)行notify()或notifyAll()方法被喚醒;
或者其他線程調(diào)用interrupt()方法钙态。
TERMINATED:如果run()方法執(zhí)行完畢慧起,或者線程異常退出,進(jìn)入TERMINATED狀態(tài)册倒。進(jìn)入此狀態(tài)后不能再調(diào)用start()方法蚓挤。

線程等待

  1. sleep()和wait()
    sleep()沒(méi)有鎖的概念,wait()有驻子。wait()方法會(huì)釋放線程本身持有的鎖屈尼。
    sleep()方法會(huì)進(jìn)入TIMED_WAITING,直到超時(shí)返回拴孤,跟鎖沒(méi)有關(guān)系脾歧,如果sleep()方法被synchronized代碼塊包含,線程本身持有的鎖不會(huì)釋放演熟。
    不帶參數(shù)的wait()方法會(huì)進(jìn)入WAITING狀態(tài)鞭执,無(wú)限期等待,直到滿足條件芒粹,或者interrupt()方法被調(diào)用兄纺。
    帶參數(shù)的wait(long millis)方法會(huì)進(jìn)入TIMED_WAITING狀態(tài),能實(shí)現(xiàn)超時(shí)返回化漆。
  2. sleep()和join()
    sleep()和join()都沒(méi)有鎖的概念估脆,只是等待返回的條件不一樣。
    不帶參數(shù)的join()方法會(huì)進(jìn)入WAITING狀態(tài)座云,無(wú)限期等待疙赠,直到該線程返回付材,或者interrupt()方法被調(diào)用。
    帶參數(shù)的join(long millis)方法會(huì)進(jìn)入TIMED_WAITING狀態(tài)圃阳,能實(shí)現(xiàn)超時(shí)返回厌衔。
  3. sleep()和yield()
    sleep()和yield()都沒(méi)有鎖的概念。
    yield()方法不會(huì)改變線程的狀態(tài)捍岳,只是讓出CPU使用權(quán)富寿。有可能下一次CPU會(huì)立即執(zhí)行。
    sleep()方法會(huì)讓出CPU使用權(quán)锣夹,直到超時(shí)返回页徐。
  4. sleep()和suspend()
    suspend()方法不會(huì)改變線程狀態(tài),依然是RUNNABLE银萍,但是被暫停了不會(huì)執(zhí)行泞坦,只有通過(guò)resume()方法能夠喚醒。suspend()和resume()已經(jīng)不推薦使用了砖顷。

線程安全

多線程并發(fā)可能會(huì)遇到的問(wèn)題是同樣的輸入,每次的輸出不一樣赃梧,需要了解Java內(nèi)存模型(JMM)的幾個(gè)概念:

  1. 原子性:操作中途是不能被其他線程打斷的滤蝠;
  2. 可見(jiàn)性:一個(gè)線程對(duì)共享變量的修改,其他線程是立即可見(jiàn)的授嘀;
  3. 有序性:在多核處理器的環(huán)境下物咳,代碼的執(zhí)行順序是沒(méi)保障的,需要其他條件來(lái)保證蹄皱。
    在Java內(nèi)存模型中览闰,允許編譯器和處理器對(duì)指令進(jìn)行重排序。重排序過(guò)程不會(huì)影響到單線程程序的執(zhí)行巷折,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性压鉴。


    內(nèi)存模型.jpg

為了實(shí)現(xiàn)操作的原子性,可見(jiàn)性和有序性锻拘,通常會(huì)用到鎖油吭。
Java提供了synchronized關(guān)鍵字來(lái)實(shí)現(xiàn)內(nèi)部鎖。被 synchronized 關(guān)鍵字修飾的方法和代碼塊叫同步方法和同步代碼塊署拟。

  1. synchronized 方法
    鎖的對(duì)象是每個(gè)類實(shí)例婉宰,只有兩個(gè)線程對(duì)同一個(gè)類實(shí)例的synchronized方法進(jìn)行訪問(wèn)才會(huì)競(jìng)爭(zhēng)鎖資源,分別對(duì)兩個(gè)實(shí)例的同一個(gè)synchronized方法訪問(wèn)是不影響的推穷。
    例如下面這個(gè)例子:
    當(dāng)兩個(gè)線程分別去訪問(wèn)同個(gè)對(duì)象的兩個(gè)synchronized方法時(shí)心包,就可能出現(xiàn)時(shí)序問(wèn)題。
    如果count()方法先執(zhí)行馒铃,兩個(gè)線程都能夠正常運(yùn)行蟹腾。
    如果waitCount()方法先執(zhí)行痕惋,就會(huì)出現(xiàn)死鎖,waitCount線程進(jìn)入死循環(huán)岭佳,一直在TIMED_WAITING狀態(tài)血巍,count線程沒(méi)有獲取到對(duì)象鎖,一直在BLOCKED狀態(tài)珊随。
public class ThreadTest {
    
    private volatile int count = 0;
    
    public synchronized void count() {
        for (int i = 0 ; i < 10; i++) {
            count++;
        }
        System.out.println(Thread.currentThread().getName() + " count=" + count);
    }
    
    public synchronized void waitCount() {
        System.out.println(Thread.currentThread().getName() + " wait start");
        while (count < 10) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
        System.out.println(Thread.currentThread().getName() + " wait finish");
    }
}
        ThreadTest test = new ThreadTest();
        Thread thread1 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                test.waitCount();
            }
        }, "thread1");
        Thread thread2 = new Thread(new Runnable() {
            
            @Override
            public void run() {
                test.count();
            }
        }, "thread2");

synchronized方法保證了操作的原子性述寡,所以synchronized內(nèi)部最好不要對(duì)共享變量使用一些while判斷,防止出現(xiàn)死循環(huán)叶洞。

  1. synchronized代碼塊
    synchronized代碼塊跟synchronized方法是類似的鲫凶,只不過(guò)可以指定鎖的對(duì)象,使用比較靈活衩辟,可以把盡可能少的操作放進(jìn)同步代碼螟炫,避免其他線程等待。
    例如上面的例子艺晴,只需要把count()的同步方法改成同步代碼塊昼钻,就能解決死循環(huán)的問(wèn)題。
public class ThreadTest {
    
    private volatile int count = 0;
    
    public void count() {
        for (int i = 0 ; i < 10; i++) {
            count++;
        }
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + " count=" + count);
        }
    }
    
    public synchronized void waitCount() {
        System.out.println(Thread.currentThread().getName() + " wait start");
        while (count < 10) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
        System.out.println(Thread.currentThread().getName() + " wait finish");
    }
}
  1. 顯式鎖
    常用的顯式鎖有ReentrantLock封寞,實(shí)現(xiàn)了Lock接口的四個(gè)方法:
    lock()和lock.unlock()方法類似synchronized然评,等待鎖的過(guò)程中線程阻塞,只不過(guò)synchronized是進(jìn)入BLOCKED狀態(tài)狈究,lock()是進(jìn)入WAITING狀態(tài)碗淌。
    lockInterruptibly()可以被中斷,需要catch InterruptedException異常抖锥。
    tryLock()成功獲取鎖返回true亿眠,否則false,不會(huì)阻塞磅废。
    tryLock(long time, TimeUnit unit)纳像,阻塞等待一段時(shí)間,然后返回拯勉。
    此外還有讀寫(xiě)鎖爹耗,這里不介紹,可以看下ReadWriteLock這個(gè)類谜喊。
  2. volatile
    一般變量保存在高速緩存區(qū)潭兽,不會(huì)立刻同步到主內(nèi)存,導(dǎo)致不同線程間的數(shù)據(jù)不同步斗遏,使用volatile關(guān)鍵字就是為了保證數(shù)據(jù)的可見(jiàn)性山卦。

下面寫(xiě)了一個(gè)簡(jiǎn)單例子,兩個(gè)線程thread1和thread2會(huì)競(jìng)爭(zhēng)lock對(duì)象鎖

package com.one.thread;

import java.util.Random;

import com.one.log.Log;

public class ThreadTest {
    
    private volatile int count = 0;
    private Log log = new Log("ThreadTest");
    private Object lock = new Object();
    
    public void run() {
        thread1.start();
        thread2.start();
    }
    
    private void sleepRandom() {
        try {
            Thread.currentThread().sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            log.log("sleep interrupt");
        }
    }
    
    private Thread thread1 = new Thread(new Runnable() {
        
        @Override
        public void run() {
            try {
                thread2.join(1000);
            } catch (InterruptedException e) {
                log.log("thread1 joinInterrupt");
            }
            for (int i = 0; i < 10; i++) {
                synchronized (lock) {
                    sleepRandom();
                    count++;
                    log.log("count=" + count + " thread2=" + thread2.getState());
                    lock.notifyAll();
                }
            }
        }
    }, "thread1");
    
    private Thread thread2 = new Thread(new Runnable() {
        
        @Override
        public void run() {
            synchronized (lock) {
                sleepRandom();
                log.log("thread1=" + thread1.getState());
                while (count < 6) {
                    try {
                        lock.wait();
                        sleepRandom();
                        log.log("waitting count=" + count+ " thread1=" + thread1.getState());
                    } catch (InterruptedException e) {
                        log.log("thread2 waitInterrupt");
                        break;
                    }
                }
                log.log("thread2 waitFinished");
            }
        }
    }, "thread2");
}

這兩個(gè)線程的狀態(tài)如下


Thread.png

如果代碼中的join()方法沒(méi)有加超時(shí),就會(huì)形成死鎖账蓉。
參考:

https://www.cnblogs.com/dolphin0520/p/3920373.html
https://www.cnblogs.com/trust-freedom/p/6606594.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末枚碗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子铸本,更是在濱河造成了極大的恐慌肮雨,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箱玷,死亡現(xiàn)場(chǎng)離奇詭異怨规,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)锡足,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門波丰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人舶得,你說(shuō)我怎么就攤上這事掰烟。” “怎么了沐批?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵纫骑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我九孩,道長(zhǎng)先馆,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任捻撑,我火速辦了婚禮,結(jié)果婚禮上缤底,老公的妹妹穿的比我還像新娘顾患。我一直安慰自己,他們只是感情好个唧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布江解。 她就那樣靜靜地躺著,像睡著了一般徙歼。 火紅的嫁衣襯著肌膚如雪犁河。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天魄梯,我揣著相機(jī)與錄音桨螺,去河邊找鬼。 笑死酿秸,一個(gè)胖子當(dāng)著我的面吹牛灭翔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辣苏,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肝箱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼哄褒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起煌张,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤呐赡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后骏融,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體链嘀,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年绎谦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了管闷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窃肠,死狀恐怖包个,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情冤留,我是刑警寧澤碧囊,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站纤怒,受9級(jí)特大地震影響糯而,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泊窘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一熄驼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烘豹,春花似錦瓜贾、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至憔鬼,卻和暖如春龟劲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轴或。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工昌跌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人照雁。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓避矢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子审胸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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