Thinking in java 之并發(fā)其三:線程的狀態(tài)

Thinking in java 之并發(fā)其三:線程的狀態(tài)

一赃春、線程的四種狀態(tài)

在 java 中伏伯,一個(gè)線程可以處于下列四種狀態(tài)之一:

  • 新建(new):當(dāng)線程被創(chuàng)建時(shí)呆抑,它會(huì)短暫的處于這種狀態(tài)疆瑰。在這種狀態(tài)下時(shí)是辕,線程已經(jīng)分配了必需的系統(tǒng)資源囤热,并執(zhí)行了初始化。此刻線程已經(jīng)有資格獲得 cpu 時(shí)間了获三,之后調(diào)度器將把這個(gè)線程轉(zhuǎn)變?yōu)榫途w或阻塞狀態(tài)旁蔼。

  • 就緒(Runnable):在這種狀態(tài)下,只要調(diào)度器把時(shí)間片分給線程疙教,線程就可以運(yùn)行棺聊。也就是說(shuō),在這種狀態(tài)下贞谓,線程是可以運(yùn)行也可以不運(yùn)行的限佩。只要調(diào)度器把時(shí)間片分給線程,線程立刻可以運(yùn)行裸弦。這是就緒狀態(tài)與阻塞或死亡狀態(tài)的區(qū)別祟同。

  • 阻塞(Blocked):線程能夠運(yùn)行,但有某個(gè)條件阻止了它的運(yùn)行理疙。當(dāng)線程進(jìn)入阻塞狀態(tài)時(shí)晕城,調(diào)度器將忽略線程,不會(huì)將 cpu 時(shí)間分配給它窖贤。一個(gè)任務(wù)進(jìn)入到阻塞狀態(tài)砖顷,通常有以下幾個(gè)原因:

    • 通過(guò)調(diào)用 sleep() 使任務(wù)進(jìn)入休眠狀態(tài);
    • 通過(guò)調(diào)用 wait() 使線程掛起赃梧;
    • 任務(wù)在等待某個(gè)輸入/輸出完成滤蝠;
    • 任務(wù)試圖在某個(gè)對(duì)象上調(diào)用其同步控制方法。
  • 死亡(dead):該狀態(tài)下授嘀,線程不可能再被調(diào)度物咳,并且再也不會(huì)得到 cpu 時(shí)間,它的任務(wù)已結(jié)束粤攒。任務(wù)死亡的方式是從 run() 方法返回所森。

二囱持、終結(jié)任務(wù)

在一些情況下夯接,我們會(huì)希望我們的線程能夠在運(yùn)行一段時(shí)間后終止。一種做法是纷妆,在 Runnable 里添加一個(gè)狀態(tài)標(biāo)識(shí)碼盔几,通過(guò)這個(gè)狀態(tài)碼來(lái)控制任務(wù)是否繼續(xù)進(jìn)行或者結(jié)束。下面就是這種方法的一個(gè)例子:

package ThreadTest.SycnSourceTest.concurrency;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class Count{
    private int count=0;
    private Random rand=new Random(47);
    public synchronized int increment() {
        int temp=count;
        if(rand.nextBoolean()) Thread.yield();
        return (count = ++temp);
    }
    public synchronized int value() {
        return count;
    }
}

class Entrance implements Runnable{

    private static Count count = new Count();
    private static List<Entrance> entrances = new ArrayList<Entrance>();
    private int number = 0;
    private final int id;
    private static volatile boolean canceled = false;
    public static void cancel() {canceled = true;}
    public Entrance(int id) {
        this.id = id;
        entrances.add(this);
    }

    @Override
    public void run() {
        while(!canceled) {
            synchronized(this) {
                ++number;
            }
            System.out.println(this+" total: " + count.increment());
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("Stopping "+this);
    }

    public synchronized int getValue(){return number;}
    public String toString() {
        return "Entrances " + id +": " + getValue();
    }
    public static int getTotalCount() {
        return count.value();
    }
    public static int sumEntrances() {
        int sum=0;
        for(Entrance entrance:entrances) {
            sum+=entrance.getValue();
        }
        return sum;
    }
}
public class OrnametalGarden {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i=0;i<5;i++) {
            exec.execute(new Entrance(i));
        }
        TimeUnit.SECONDS.sleep(3);
        Entrance.cancel();
        exec.shutdown();
        if(!exec.awaitTermination(250, TimeUnit.MILLISECONDS))
            System.out.println("Some task were not terminated");
            System.out.println("Total: "+Entrance.getTotalCount());
            System.out.println("Sum of Entrances: "+Entrance.sumEntrances());
    }
}

我們通過(guò)布爾變量 cannel 來(lái)控制任務(wù)是否應(yīng)該終止掩幢,當(dāng) main 的線程進(jìn)行到某一時(shí)刻時(shí)逊拍,我們將 cannel 置為 true (此處的 cannel 是volatile 的上鞠,所以它的改變會(huì)立刻被其他任務(wù)捕捉到),從而終止所有正在進(jìn)行的任務(wù)芯丧。

有趣的時(shí)芍阎,我們從結(jié)果中不難發(fā)現(xiàn),計(jì)數(shù)器并不是遞增的缨恒,它會(huì)出現(xiàn)跳躍的情況谴咸。1 2 4 3 6 5... 這說(shuō)明,雖然某個(gè)任務(wù)得以先進(jìn)行骗露,但未必會(huì)第一個(gè)完成岭佳。

java 的 concurrency 包也為我們提供了中斷線程的方法。在第一篇線程文章里萧锉,我們使用了 Future 實(shí)現(xiàn)了讓 run() 返回特定類(lèi)型的信息珊随。Future 也可以幫我們實(shí)現(xiàn)中斷線程的操作。

如果我們?cè)谑褂?Excutor 來(lái)啟動(dòng)線程時(shí)柿隙,不使用 executor() 而是使用 submit()叶洞,我們就可以獲得一個(gè) Future<?> 。這個(gè) Future 是持有任務(wù)的上下文的优俘,我們可以通過(guò)它的 cancel 方法來(lái)實(shí)現(xiàn)中斷線程的操作京办。

package ThreadTest.ThreadStatus;

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

class SleepBlocked implements Runnable{
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(100);
        }catch(InterruptedException e) {
            System.out.println("Catch InterruptedExcetpion");
        }
        System.out.println("Exiting SleepBlocked run()");
    }
}

class IOBlocked implements Runnable{
    private InputStream in;
    public IOBlocked(InputStream is) {
        in = is;
    }
    public void run() {
        try {
            System.out.println("Waiting for read()");
            in.read();
        }catch(IOException e) {
            if(Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupted from block I/O");
            }else {
                throw new RuntimeException(e);
            }
        }
        System.out.println("Exiting IOBlocked.run()");
    }
}

class SynchronizedBlocked implements Runnable{
    public synchronized void f() {
        while(true) {
            Thread.yield();
        }
    }
    public SynchronizedBlocked() {
        new Thread() {
            public void run() {
                f();
            }
        }.start();
    }
    public void run() {
        System.out.println("Trying to call f()");
        f();
        System.out.println("Exiting SynchronizedBlocked r()");
    }
}
public class Inturrupting {

    public static ExecutorService exec = Executors.newCachedThreadPool();
    static void test(Runnable r) throws InterruptedException{
        Future<?> f = exec.submit(r);
        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("Interrupting "+r.getClass().getName());
        f.cancel(true);
        System.out.println("Interrupt sent to "+r.getClass().getName());
    }
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        test(new SleepBlocked());
        test(new IOBlocked(System.in));
        test(new SynchronizedBlocked());
        TimeUnit.SECONDS.sleep(3);
        System.out.println("Aborting with system.exit(0)");
        System.exit(0);

    }

}

在這個(gè)示例中,我們一共對(duì)3中阻塞情況進(jìn)行了中斷任務(wù)操作帆焕。

對(duì)于 sleep() 引起的阻塞惭婿,在我們通過(guò) Future 對(duì)其進(jìn)行了中斷操作之后,任務(wù)跑出了 InturruptedException 異常叶雹,證明了任務(wù)的確被中斷财饥。

另外兩種情況(IO 阻塞和等待鎖阻塞)我們并沒(méi)有得到它們被中斷的輸出。這會(huì)導(dǎo)致一些問(wèn)題折晦,尤其是在創(chuàng)建 IO 的任務(wù)是钥星,我們可能會(huì)被 IO 鎖住多線程程序。

一個(gè)比較笨拙的解決方式是關(guān)閉任務(wù)在其上發(fā)生阻塞的底層資源满着。

(此處本該有示例谦炒,但是運(yùn)行結(jié)果并沒(méi)有符合預(yù)期,目前原因未知)

Java 的 IO 的 nio 類(lèi)還為我們提供更加人性化 IO 中斷操作风喇。被阻塞的 nio 通道會(huì)自動(dòng)的響應(yīng)中斷宁改。

至于由于等待鎖而造成的阻塞,Java 的 ReentrantLock 具備阻塞時(shí)中斷的功能魂莫。

package ThreadTest.ThreadStatus;

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

class BlockedMutex{
    private Lock lock = new ReentrantLock();
    public BlockedMutex() {
        lock.lock();
    }

    public void f() {
        try {
            lock.lockInterruptibly();
            System.out.println("lock acquire in f()");
        }catch(InterruptedException e) {
            System.out.println("Interrupted from lock acquisitiong in f()");
        }
    }
}

class Blocked2 implements Runnable{
    BlockedMutex block = new BlockedMutex();
    public void run() {
        System.out.println("wait for f() in BlockedMuex");
        block.f();
        System.out.println("Broken out of blocked call");
    }
}
public class Interrupting2 {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Blocked2());
        t.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Issuing t.interrupt()");
        t.interrupt();
    }

}

BlokedMutex 類(lèi)的構(gòu)造器會(huì)獲取所創(chuàng)建對(duì)象上自身的 lock还蹲,并且我們沒(méi)有在任何地方去釋放這個(gè)鎖。所以當(dāng)其他任務(wù)想要調(diào)用 f() 時(shí),將會(huì)因?yàn)镸utex不可獲得而被阻塞谜喊。在Blcked2中潭兽,run() 方法總是在調(diào)用 f() 的地方停止。與 I/O 調(diào)用不同斗遏,interript() 可以打斷被互斥鎖阻塞的調(diào)用山卦。

如果我們編寫(xiě)的程序有線程中斷的可能,那么為了避免 run() 里面的循環(huán)能夠檢測(cè)到線程被中斷并且正確退出(而不是通過(guò)拋出異常的方式退出)诵次。檢測(cè)的方式可以利用 Thread.interrupted() 實(shí)現(xiàn):

package ThreadTest.ThreadStatus;

import java.util.concurrent.TimeUnit;

class NeedsCleanup{
    private final int id;
    public NeedsCleanup(int ident) {
        this.id = ident;
        System.out.println("NeedsCleanUp: " + id);
    }
    public void cleanup(){
        System.out.println("cleaning up " + id);
    }

}

class Blocked3 implements Runnable{
    private volatile double d = 0.0;
    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                NeedsCleanup n1 = new NeedsCleanup(1);
                try {
                    System.out.println("Sleeping");
                    TimeUnit.SECONDS.sleep(1);
                    NeedsCleanup n2 = new NeedsCleanup(2);
                    try {
                        System.out.println("Calculation");
                        for(int i=1;i<2500000;i++) {
                            d=d+(Math.PI+Math.E)/d;
                        }
                        System.out.println("Finished time-consuming operation");
                    }finally {
                        n2.cleanup();
                    }
                }finally{
                    n1.cleanup();
                }
            }
            System.out.println("Exiting via while() test");
        }catch(InterruptedException e) {
            System.out.println("Exiting via InterruptedException");
        }

    }
}
public class InterruptingIdiom {

    private static int tm = 1002;
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Blocked3());
        t.start();
        TimeUnit.MILLISECONDS.sleep(tm);
        t.interrupt();
    }

}

在這個(gè)示例中 NeedsCleanup 表示一個(gè)必須要做清理操作的類(lèi)怒坯。我們使用 try-finally 來(lái)保證它的清理方法 cleanup 總是被調(diào)用。

通過(guò)調(diào)節(jié) tm 的值藻懒,我們可以控制程序在 sleep 階段或者在 calculation 階段停止剔猿。當(dāng)在 sleep 階段停止時(shí),任務(wù)會(huì)以拋出異常的方式退出嬉荆,而在 calculation 階段停止時(shí)归敬,任務(wù)會(huì)在 while() 的判斷處被中斷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鄙早,一起剝皮案震驚了整個(gè)濱河市汪茧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌限番,老刑警劉巖舱污,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異弥虐,居然都是意外死亡扩灯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)霜瘪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)珠插,“玉大人,你說(shuō)我怎么就攤上這事颖对∧沓牛” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵缤底,是天一觀的道長(zhǎng)顾患。 經(jīng)常有香客問(wèn)我,道長(zhǎng)个唧,這世上最難降的妖魔是什么江解? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮坑鱼,結(jié)果婚禮上膘流,老公的妹妹穿的比我還像新娘。我一直安慰自己鲁沥,他們只是感情好呼股,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著画恰,像睡著了一般彭谁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上允扇,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天缠局,我揣著相機(jī)與錄音,去河邊找鬼考润。 笑死狭园,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的糊治。 我是一名探鬼主播唱矛,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼井辜!你這毒婦竟也來(lái)了绎谦?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤粥脚,失蹤者是張志新(化名)和其女友劉穎窃肠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體刷允,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冤留,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了树灶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搀菩。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖破托,靈堂內(nèi)的尸體忽然破棺而出肪跋,到底是詐尸還是另有隱情,我是刑警寧澤土砂,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布州既,位于F島的核電站,受9級(jí)特大地震影響萝映,放射性物質(zhì)發(fā)生泄漏吴叶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一序臂、第九天 我趴在偏房一處隱蔽的房頂上張望蚌卤。 院中可真熱鬧实束,春花似錦、人聲如沸逊彭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)侮叮。三九已至避矢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間囊榜,已是汗流浹背审胸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卸勺,地道東北人砂沛。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像曙求,于是被迫代替她去往敵國(guó)和親尺上。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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

  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書(shū)筆記圆到,整理的知識(shí)點(diǎn)怎抛,也是為了防止忘記,尊重勞動(dòng)成果芽淡,轉(zhuǎn)載注明出處哦马绝!如果你也喜歡,那...
    波波波先森閱讀 11,270評(píng)論 4 56
  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過(guò)程中...
    勝浩_ae28閱讀 5,110評(píng)論 0 23
  • 任務(wù)和線程的啟動(dòng)很容易挣菲。 在大多數(shù)時(shí)候富稻, 我們都會(huì)讓它們運(yùn)行直到結(jié)束,或者讓它們自行停止白胀。然而椭赋,有時(shí)候我們希望提前...
    好好學(xué)習(xí)Sun閱讀 1,152評(píng)論 0 0
  • 單任務(wù) 單任務(wù)的特點(diǎn)是排隊(duì)執(zhí)行,也就是同步或杠,就像再cmd輸入一條命令后哪怔,必須等待這條命令執(zhí)行完才可以執(zhí)行下一條命令...
    Steven1997閱讀 1,178評(píng)論 0 6
  • 1.GCD GCD(Grand Central Dispatch) 偉大的中樞調(diào)度器GCD是蘋(píng)果為多核的并行運(yùn)算提...
    Nbm閱讀 246評(píng)論 0 3