Thread 基礎(chǔ)知識

線程

線程(Thread)是java程序運(yùn)行的基本調(diào)度單元; 在進(jìn)行JUC的源碼分析之前, 想回顧一下Thread的基本屬性, 及方法;

1. 線程 State:
/**
 * Created by xjk on 1/12/17.
 */
public enum State {
    /** 線程剛剛創(chuàng)建時的狀態(tài), 馬上到 RUNNABLE */
    NEW,
    
    /** 線程初始化OK, 開始執(zhí)行任務(wù)(run) */
    RUNNABLE,
    
    /**
     * 阻塞狀態(tài), 千萬別和WAITING狀態(tài)混淆
     * 這種狀態(tài)是線程在等待 JVM monitor lock(通俗一點(diǎn) 就是等待執(zhí)行 synchronous 里面的代碼)
     * 這和 LockSupport 沒半毛錢關(guān)系
     */
    BLOCKED,
    
    /**
     * 線程的等待狀態(tài), 導(dǎo)致線程進(jìn)入這種狀態(tài)通常是下面三個方法
     * 1. Object.wait()
     * 2. Thread.join()
     * 3. LockSupport.park()
     */
    WAITING,
    
    /**
     * 這也是線程的等待狀態(tài), 和WAITING差不多, 只是這個有timeout而已, 通常由下面四種方法導(dǎo)致
     * 1. Object.wait(long timeout)
     * 2. Thread.join(long timeout)
     * 3. LockSupport.parkNanos(long timeout)
     * 4. LockSupport.parkUntil(long timeout)
     */
    TIMED_WAITING,

    /**
     * 線程執(zhí)行ok
     */
    TERMINATED
}

這些狀態(tài)何時有用, 我又何時見到它們呢? 對, 一般線程出問題就會想到它們了(比如 程序死鎖, 直接運(yùn)行 jstack 查看線程堆棧, 就能大體判斷問題出在哪個線程, 哪段代碼)

2. 常用方法
/** 等待 */
Object.wait();
/** 通知 */
Object.notify();
/** 懸掛 */
Thread.suspend();
/** 重用 */
Thread.resume();
/** 等待x線程執(zhí)行后, 當(dāng)前線程再執(zhí)行 */
Thread.join();
/**
 * A hint to the scheduler that the current thread is willing to yield
 * its current use of a processor. The scheduler is free to ignore this
 * hint.
 * 這段英語大體意思: 給調(diào)度器發(fā)送信息, 當(dāng)前線程推出CPU調(diào)度(這個不是指當(dāng)前線程不執(zhí)行任務(wù))
 * 調(diào)用這個方法后, 當(dāng)前線程會先推出任務(wù)調(diào)度, 然后再重新?lián)寠ZCPU, 但能不能搶到就不一定了
 * 通產(chǎn)用于, 當(dāng)前線程占用較多資源, 但任務(wù)又不緊急的情況(concurrent包中的源碼會提及)
 */
Thread.yield();

這幾個是線程的基本用法, 但現(xiàn)在用得比較少, 為啥?

  1. jdk中有了更好用的 concurrent 包開進(jìn)行多線程開發(fā)
  2. 使用這些方法有時會出現(xiàn)奇怪的事件(尤其和 concurrent 包中的工具類混用, 匪夷所思)
    ps: 若想用這些方法來寫 Future, Promise 等工具類 可以 參考Netty 4.x系列
3. 線程中斷

線程中斷是java中線程協(xié)作的重要機(jī)制, 而所謂的中斷其實(shí)只是給線程設(shè)置中斷標(biāo)示, 并且喚醒正在waiting狀態(tài)的線程;
線程中斷相關(guān)的主要有三個方法:


/**
 * 作用: 中斷線程,
 * 若線程 sleep 或 wait 時調(diào)用此方法,
 * 則拋出 InterruptedException 異常, 并且會清除中斷標(biāo)記
 * (ps 重點(diǎn)來了, 若通過 LockSupport阻塞線程, 則不會拋出異常, 并且不會清除線程的中斷標(biāo)記, 這在 concurrent 包里面充分利用了這個機(jī)制)
 *
 * 比如先通過 LockSupport.park(this) 來中斷, 而后其他線程釋放lock時, 喚醒這個線程, 這時再調(diào)用 Thread.interrupted() 返回中斷標(biāo)示(調(diào)用此方法會清除中斷標(biāo)示)
 *  這時外面的函數(shù)會根據(jù) parkAndCheckInterrupt() 函數(shù)的返回值判斷線程的喚醒是被 interrupted 還是正常的喚醒(LockSupport.unpark()) 來決定后續(xù)的策略
 * private final boolean parkAndCheckInterrupt() {
 *     LockSupport.park(this);
 *     return Thread.interrupted();
 * }
 */
Thread.interrupt();

/**
 * 判斷當(dāng)前的線程是否中斷, 返回 true/false
 */
Thread.isInterrupted();

/**
 * 判斷當(dāng)前的線程是否中斷, 并且清除中斷標(biāo)示(注意這里是 interrupted, 和上面的 interrupt是不一樣的)
 */
Thread.interrupted();

線程的中斷是 Java 并發(fā)開發(fā)中非常重要的機(jī)制, concurrent 包中的很多工具類的方法都是通過這個機(jī)制來安全退出;

下面來段代碼示例一下:


import org.apache.log4j.Logger;

import java.util.concurrent.locks.LockSupport;

/**
 * Created by xjk on 1/13/17.
 */
public class ThreadTest {

    private static final Logger logger = Logger.getLogger(ThreadTest.class);

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

        Thread t1 = new Thread(){
            @Override
            public void run() {
                while(true){
                    if(Thread.currentThread().isInterrupted()){
                        logger.info("線程中斷, 退出loop");
                        break;
                    }

                    try {
                        Thread.sleep(5*1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        logger.info("線程在 waiting 狀態(tài)時收到中斷信息");
                        logger.info("此時線程中斷標(biāo)示: " + Thread.currentThread().isInterrupted());
                        // 再次點(diǎn)用線程中斷, 這時就又有中斷標(biāo)示
                        Thread.currentThread().interrupt();
                    }

                    Thread.yield();
                }
                logger.info("1. 此時線程中斷標(biāo)示: " + Thread.currentThread().isInterrupted());

                // 再次調(diào)用程序阻塞, 看看是否有用
                LockSupport.park(this);
                logger.info("2. 此時線程中斷標(biāo)示: " + Thread.currentThread().isInterrupted());
                try {
                    Thread.currentThread().sleep(5*1000);
                } catch (InterruptedException e) {
                    logger.info("在線程中斷時調(diào)用sleep 拋異常");
                    e.printStackTrace();
                }


                logger.info("3. 此時線程中斷標(biāo)示: " + Thread.currentThread().interrupted());

            }
        };

        t1.start();
        Thread.sleep(2 *1000);
        t1.interrupt();
    }
}

這段代碼主要測試線程中斷的幾個函數(shù)功能, 建議自己寫一下, 在運(yùn)行程序時, 會發(fā)現(xiàn) 程序中的'LockSupport.park(this);' 沒起作用, 而且還沒清除中斷標(biāo)記; 但此時調(diào)用 Thread.sleep(long timeout), 則會因?yàn)橹袛鄻?biāo)示的存在而拋出異常(拋出異常后中斷標(biāo)示也被清除);
執(zhí)行結(jié)果

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.lami.tuomatuo.search.base.concurrent.thread.ThreadTest$1.run(ThreadTest.java:26)
[2017-01-13 22:23:03,903] INFO  Thread-0 (ThreadTest.java:29) - 線程在 waiting 狀態(tài)時收到中斷信息
[2017-01-13 22:23:03,906] INFO  Thread-0 (ThreadTest.java:30) - 此時線程中斷標(biāo)示: false
[2017-01-13 22:23:03,907] INFO  Thread-0 (ThreadTest.java:21) - 線程中斷, 退出loop
[2017-01-13 22:23:03,907] INFO  Thread-0 (ThreadTest.java:37) - 1. 此時線程中斷標(biāo)示: true
[2017-01-13 22:23:03,908] INFO  Thread-0 (ThreadTest.java:41) - 2. 此時線程中斷標(biāo)示: true
[2017-01-13 22:23:03,909] INFO  Thread-0 (ThreadTest.java:45) - 在線程中斷時調(diào)用sleep 拋異常
[2017-01-13 22:23:03,909] INFO  Thread-0 (ThreadTest.java:50) - 3. 此時線程中斷標(biāo)示: false
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.lami.tuomatuo.search.base.concurrent.thread.ThreadTest$1.run(ThreadTest.java:43)
4. Daemon進(jìn)程

Daemon線程: 守護(hù)線程通常是在后臺默默干一些苦活, 比如垃圾回收; 守護(hù)線程有個特點(diǎn), 就是當(dāng)那些daemon=false的線程都退出, 則daemon也退出
直接上代碼:

import org.apache.log4j.Logger;

/**
 * Created by xjk on 1/13/17.
 */
public class DaemonThreadTest {

    private static final Logger logger = Logger.getLogger(DaemonThreadTest.class);

    static Thread t1 = new Thread(){
        @Override
        public void run() {
            while(true){
               logger.info("I am alive");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            logger.info("我要退出程序了");
        }
    };

    public static void main(String[] args) throws Exception{
        t1.setDaemon(true);
        t1.start();

        Thread.sleep(3 * 1000);

        logger.info("main 方法執(zhí)行OK");

    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        logger.info("DaemonThreadTest 退出");
    }

}

線程t1 daemon=true, 所以在主線程退出后, 自己也退出, 自己在運(yùn)行時,可以將 "t1.setDaemon(true)" 注釋掉再看看效果
ps:

  1. finalize在java中不一定會執(zhí)行
  2. daemon 默認(rèn)值是 false

console:

[2017-01-13 22:43:27,605] INFO  Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:28,612] INFO  Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:29,615] INFO  Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:30,606] INFO  main (DaemonThreadTest.java:38) - main 方法執(zhí)行OK

Process finished with exit code 0

總結(jié): 線程中斷機(jī)制在整個并發(fā)編程中起著非常大的作用, 尤其和 LockSupport 配合使用

參考資料:
skywang12345 線程 interrupt
zhanjindong LockSupport和Interrupt (這篇寫得相當(dāng)好??)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末士修,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子樱衷,更是在濱河造成了極大的恐慌棋嘲,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箫老,死亡現(xiàn)場離奇詭異封字,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門阔籽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來流妻,“玉大人,你說我怎么就攤上這事笆制∩鹫猓” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵在辆,是天一觀的道長证薇。 經(jīng)常有香客問我,道長匆篓,這世上最難降的妖魔是什么浑度? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鸦概,結(jié)果婚禮上箩张,老公的妹妹穿的比我還像新娘。我一直安慰自己窗市,他們只是感情好先慷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咨察,像睡著了一般论熙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上摄狱,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天脓诡,我揣著相機(jī)與錄音,去河邊找鬼二蓝。 笑死誉券,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刊愚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼踩验,長吁一口氣:“原來是場噩夢啊……” “哼鸥诽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起箕憾,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤牡借,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后袭异,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钠龙,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碴里。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沈矿。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咬腋,靈堂內(nèi)的尸體忽然破棺而出羹膳,到底是詐尸還是另有隱情,我是刑警寧澤根竿,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布陵像,位于F島的核電站,受9級特大地震影響寇壳,放射性物質(zhì)發(fā)生泄漏醒颖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一壳炎、第九天 我趴在偏房一處隱蔽的房頂上張望泞歉。 院中可真熱鬧,春花似錦冕广、人聲如沸疏日。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沟优。三九已至,卻和暖如春睬辐,著一層夾襖步出監(jiān)牢的瞬間挠阁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工溯饵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侵俗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓丰刊,卻偏偏與公主長得像隘谣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子啄巧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

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

  • 一:java概述:1寻歧,JDK:Java Development Kit,java的開發(fā)和運(yùn)行環(huán)境秩仆,java的開發(fā)工...
    ZaneInTheSun閱讀 2,651評論 0 11
  • 下面是我自己收集整理的Java線程相關(guān)的面試題码泛,可以用它來好好準(zhǔn)備面試。 參考文檔:-《Java核心技術(shù) 卷一》-...
    阿呆變Geek閱讀 14,832評論 14 507
  • Java8張圖 11澄耍、字符串不變性 12噪珊、equals()方法晌缘、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,707評論 0 11
  • 前言 多線程并發(fā)編程是Java編程中重要的一塊內(nèi)容痢站,也是面試重點(diǎn)覆蓋區(qū)域磷箕,所以學(xué)好多線程并發(fā)編程對我們來說極其重要...
    嘟爺MD閱讀 7,315評論 21 272
  • 2014年,7月瑟押,漫長度過青春的我開始奔跑了搀捷。 悶熱下午,我正蹲在家里玩游戲“哈撒ki多望,”噪雜的聲音電腦前的一...
    笑妄想閱讀 249評論 0 0