Thinking in java 之并发其三:线程的状态

一、线程的四种状态

在 java 中,一个线程可以处于下列四种状态之一:

  1)新建(new):当线程被创建时,它会短暂的处于这种状态。在这种状态下时,线程已经分配了必需的系统资源,并执行了初始化。此刻线程已经有资格获得 cpu 时间了,之后调度器将把这个线程转变为就绪或阻塞状态。

  2)就绪(Runnable):在这种状态下,只要调度器把时间片分给线程,线程就可以运行。也就是说,在这种状态下,线程是可以运行也可以不运行的。只要调度器把时间片分给线程,线程立刻可以运行。这是就绪状态与阻塞或死亡状态的区别。

  3)阻塞(Blocked):线程能够运行,但有某个条件阻止了它的运行。当线程进入阻塞状态时,调度器将忽略线程,不会将 cpu 时间分配给它。一个任务进入到阻塞状态,通常有以下几个原因:

  1. 通过调用 sleep() 使任务进入休眠状态;
  2. 通过调用 wait() 使线程挂起;
  3. 任务在等待某个输入/输出完成;
  4. 任务试图在某个对象上调用其同步控制方法。

  4)死亡(dead):该状态下,线程不可能再被调度,并且再也不会得到 cpu 时间,它的任务已结束。任务死亡的方式是从 run() 方法返回。

 

二、终结任务

在一些情况下,我们会希望我们的线程能够在运行一段时间后终止。一种做法是,在 Runnable 里添加一个状态标识码,通过这个状态码来控制任务是否继续进行或者结束。下面就是这种方法的一个例子:

 1 package ThreadTest.SycnSourceTest.concurrency;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.Random;
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 import java.util.concurrent.TimeUnit;
 9 
10 class Count{
11     private int count=0;
12     private Random rand=new Random(47);
13     public synchronized int increment() {
14         int temp=count;
15         if(rand.nextBoolean()) Thread.yield();
16         return (count = ++temp);
17     }
18     public synchronized int value() {
19         return count;
20     }
21 }
22 
23 class Entrance implements Runnable{
24 
25     private static Count count = new Count();
26     private static List<Entrance> entrances = new ArrayList<Entrance>();
27     private int number = 0;
28     private final int id;
29     private static volatile boolean canceled = false;
30     public static void cancel() {canceled = true;}
31     public Entrance(int id) {
32         this.id = id;
33         entrances.add(this);
34     }
35     
36     @Override
37     public void run() {
38         while(!canceled) {
39             synchronized(this) {
40                 ++number;
41             }
42             System.out.println(this+" total: " + count.increment());
43             try {
44                 TimeUnit.MILLISECONDS.sleep(100);
45             } catch (InterruptedException e) {
46                 // TODO Auto-generated catch block
47                 e.printStackTrace();
48             }
49         }
50         System.out.println("Stopping "+this);
51     }
52     
53     public synchronized int getValue(){return number;}
54     public String toString() {
55         return "Entrances " + id +": " + getValue();
56     }
57     public static int getTotalCount() {
58         return count.value();
59     }
60     public static int sumEntrances() {
61         int sum=0;
62         for(Entrance entrance:entrances) {
63             sum+=entrance.getValue();
64         }
65         return sum;
66     }
67 }
68 public class OrnametalGarden {
69     public static void main(String[] args) throws InterruptedException {
70         ExecutorService exec = Executors.newCachedThreadPool();
71         for(int i=0;i<5;i++) {
72             exec.execute(new Entrance(i));
73         }
74         TimeUnit.SECONDS.sleep(3);
75         Entrance.cancel();
76         exec.shutdown();
77         if(!exec.awaitTermination(250, TimeUnit.MILLISECONDS))
78             System.out.println("Some task were not terminated");
79             System.out.println("Total: "+Entrance.getTotalCount());
80             System.out.println("Sum of Entrances: "+Entrance.sumEntrances());
81     }
82 }

我们通过布尔变量 cannel 来控制任务是否应该终止,当 main 的线程进行到某一时刻时,我们将 cannel 置为 true (此处的 cannel 是volatile 的,所以它的改变会立刻被其他任务捕捉到),从而终止所有正在进行的任务。

有趣的时,我们从结果中不难发现,计数器并不是递增的,它会出现跳跃的情况。1 2 4 3 6 5... 这说明,虽然某个任务得以先进行,但未必会第一个完成。

java 的 concurrency 包也为我们提供了中断线程的方法。在第一篇线程文章里,我们使用了 Future 实现了让 run() 返回特定类型的信息。Future 也可以帮我们实现中断线程的操作。

如果我们在使用 Excutor 来启动线程时,不使用 executor() 而是使用 submit(),我们就可以获得一个 Future<?> 。这个 Future 是持有任务的上下文的,我们可以通过它的 cancel 方法来实现中断线程的操作。

 1 package ThreadTest.ThreadStatus;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.util.concurrent.ExecutorService;
 6 import java.util.concurrent.Executors;
 7 import java.util.concurrent.Future;
 8 import java.util.concurrent.TimeUnit;
 9 
10 class SleepBlocked implements Runnable{
11     public void run() {
12         try {
13             TimeUnit.SECONDS.sleep(100);
14         }catch(InterruptedException e) {
15             System.out.println("Catch InterruptedExcetpion");
16         }
17         System.out.println("Exiting SleepBlocked run()");
18     }
19 }
20 
21 class IOBlocked implements Runnable{
22     private InputStream in;
23     public IOBlocked(InputStream is) {
24         in = is;
25     }
26     public void run() {
27         try {
28             System.out.println("Waiting for read()");
29             in.read();
30         }catch(IOException e) {
31             if(Thread.currentThread().isInterrupted()) {
32                 System.out.println("Interrupted from block I/O");
33             }else {
34                 throw new RuntimeException(e);
35             }
36         }
37         System.out.println("Exiting IOBlocked.run()");
38     }
39 }
40 
41 class SynchronizedBlocked implements Runnable{
42     public synchronized void f() {
43         while(true) {
44             Thread.yield();
45         }
46     }
47     public SynchronizedBlocked() {
48         new Thread() {
49             public void run() {
50                 f();
51             }
52         }.start();
53     }
54     public void run() {
55         System.out.println("Trying to call f()");
56         f();
57         System.out.println("Exiting SynchronizedBlocked r()");
58     }
59 } 
60 public class Inturrupting {
61     
62     public static ExecutorService exec = Executors.newCachedThreadPool();
63     static void test(Runnable r) throws InterruptedException{
64         Future<?> f = exec.submit(r);
65         TimeUnit.MILLISECONDS.sleep(100);
66         System.out.println("Interrupting "+r.getClass().getName());
67         f.cancel(true);
68         System.out.println("Interrupt sent to "+r.getClass().getName());
69     }
70     public static void main(String[] args) throws InterruptedException {
71         // TODO Auto-generated method stub
72         test(new SleepBlocked());
73         test(new IOBlocked(System.in));
74         test(new SynchronizedBlocked());
75         TimeUnit.SECONDS.sleep(3);
76         System.out.println("Aborting with system.exit(0)");
77         System.exit(0);
78 
79     }
80 
81 }

在这个示例中,我们一共对3中阻塞情况进行了中断任务操作。

对于 sleep() 引起的阻塞,在我们通过 Future 对其进行了中断操作之后,任务跑出了 InturruptedException 异常,证明了任务的确被中断。

另外两种情况(IO 阻塞和等待锁阻塞)我们并没有得到它们被中断的输出。这会导致一些问题,尤其是在创建 IO 的任务是,我们可能会被 IO 锁住多线程程序。

一个比较笨拙的解决方式是关闭任务在其上发生阻塞的底层资源。

(此处本该有示例,但是运行结果并没有符合预期,目前原因未知)

Java 的 IO 的 nio 类还为我们提供更加人性化 IO 中断操作。被阻塞的 nio 通道会自动的响应中断。

至于由于等待锁而造成的阻塞,Java 的 ReentrantLock 具备阻塞时中断的功能。

 1 package ThreadTest.ThreadStatus;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 import java.util.concurrent.locks.Lock;
 5 import java.util.concurrent.locks.ReentrantLock;
 6 
 7 class BlockedMutex{
 8     private Lock lock = new ReentrantLock();
 9     public BlockedMutex() {
10         lock.lock();
11     }
12     
13     public void f() {
14         try {
15             lock.lockInterruptibly();
16             System.out.println("lock acquire in f()");
17         }catch(InterruptedException e) {
18             System.out.println("Interrupted from lock acquisitiong in f()");
19         }
20     }
21 }
22 
23 class Blocked2 implements Runnable{
24     BlockedMutex block = new BlockedMutex();
25     public void run() {
26         System.out.println("wait for f() in BlockedMuex");
27         block.f();
28         System.out.println("Broken out of blocked call");
29     }
30 }
31 public class Interrupting2 {
32 
33     public static void main(String[] args) throws InterruptedException {
34         Thread t = new Thread(new Blocked2());
35         t.start();
36         TimeUnit.SECONDS.sleep(1);
37         System.out.println("Issuing t.interrupt()");
38         t.interrupt();
39     }
40 
41 }

BlokedMutex 类的构造器会获取所创建对象上自身的 lock,并且我们没有在任何地方去释放这个锁。所以当其他任务想要调用 f() 时,将会因为Mutex不可获得而被阻塞。在Blcked2中,run() 方法总是在调用 f() 的地方停止。与 I/O 调用不同,interript() 可以打断被互斥锁阻塞的调用。

如果我们编写的程序有线程中断的可能,那么为了避免 run() 里面的循环能够检测到线程被中断并且正确退出(而不是通过抛出异常的方式退出)。检测的方式可以利用 Thread.interrupted() 实现:

 1 package ThreadTest.ThreadStatus;
 2 
 3 import java.util.concurrent.TimeUnit;
 4 
 5 class NeedsCleanup{
 6     private final int id;
 7     public NeedsCleanup(int ident) {
 8         this.id = ident;
 9         System.out.println("NeedsCleanUp: " + id);
10     }
11     public void cleanup(){
12         System.out.println("cleaning up " + id);
13     }
14     
15 }
16 
17 class Blocked3 implements Runnable{
18     private volatile double d = 0.0;
19     @Override
20     public void run() {
21         try {
22             while(!Thread.interrupted()) {
23                 NeedsCleanup n1 = new NeedsCleanup(1);
24                 try {
25                     System.out.println("Sleeping");
26                     TimeUnit.SECONDS.sleep(1);
27                     NeedsCleanup n2 = new NeedsCleanup(2);
28                     try {
29                         System.out.println("Calculation");
30                         for(int i=1;i<2500000;i++) {
31                             d=d+(Math.PI+Math.E)/d;
32                         }
33                         System.out.println("Finished time-consuming operation");
34                     }finally {
35                         n2.cleanup();
36                     }
37                 }finally{
38                     n1.cleanup();
39                 }
40             }
41             System.out.println("Exiting via while() test");
42         }catch(InterruptedException e) {
43             System.out.println("Exiting via InterruptedException");
44         }
45         
46     }
47 }
48 public class InterruptingIdiom {
49 
50     private static int tm = 1002;
51     public static void main(String[] args) throws InterruptedException {
52         Thread t=new Thread(new Blocked3());
53         t.start();
54         TimeUnit.MILLISECONDS.sleep(tm);
55         t.interrupt();
56     }
57 
58 }

在这个示例中 NeedsCleanup 表示一个必须要做清理操作的类。我们使用 try-finally 来保证它的清理方法 cleanup 总是被调用。

通过调节 tm 的值,我们可以控制程序在 sleep 阶段或者在 calculation 阶段停止。当在 sleep 阶段停止时,任务会以抛出异常的方式退出,而在 calculation 阶段停止时,任务会在 while() 的判断处被中断。

 

posted @ 2018-09-09 07:47  crazy_runcheng  阅读(241)  评论(0编辑  收藏  举报