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() 的判斷處被中斷。