终结任务
cancel()和isCancel()方法被放到一个所有任务都可以看到的类中,这些任务通过检查sCancel()来确定何时终止自己。
装饰性花园
花园管理希望统计多个大门进入公园的总人数,每个大门都长有计数器,且每个大门计数值递增时,公园 的总人数的共享计数值也会递增
代码1-1
<pre name="code" class="java">import java.util.concurrent.*;
import java.util.*;
class Count {
private int count = 0;
public synchronized int increment() {
return ++count;
}
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);
}
public void run() {
while (!canceled) {
++number;
System.out.println(this + " Total: " + count.increment());
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
System.out.println("sleep interrupted");
}
}
System.out.println("Stopping " + this);
}
public synchronized int getValue() {
return number;
}
public String toString() {
return "Entrance " + 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 OrnamentalGarden {
public static void main(String[] args) throws Exception {
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 tasks were not terminated!");
}
System.out.println("Total: " + Entrance.getTotalCount());
System.out.println("Sum of Entrances: " + Entrance.sumEntrances());
}
}
代码1-1运行结果:
Entrance 0: 1 Total: 1Entrance 4: 1 Total: 5Entrance 3: 1 Total: 4Entrance 2: 1 Total: 3Entrance 1: 1 Total: 2Entrance 2: 2 Total: 6Entrance 4: 2 Total: 9Entrance 3: 2 Total: 8Entrance 1: 2 Total: 7Entrance 0: 2 Total: 10…………Entrance 4: 29 Total: 141Entrance 2: 29 Total: 142Entrance 3: 29 Total: 143Entrance 1: 29 Total: 144Entrance 0: 29 Total: 145Entrance 2: 30 Total: 146Entrance 3: 30 Total: 149Entrance 1: 30 Total: 148Entrance 4: 30 Total: 147Entrance 0: 30 Total: 150Stopping Entrance 2: 30Stopping Entrance 1: 30Stopping Entrance 3: 30Stopping Entrance 4: 30Stopping Entrance 0: 30Total: 150Sum of Entrances: 150
这里使用单个Count对象来跟踪花园参观者的共享计数值,Count.increment()和Count.value()都是synchronized,用来控制对count域进行访问。每个Entrance任务都维护一个本地值number,用来统计进入大门的人数。Entrance.canceled是一个volatile的布尔标志,而它只会被读取和赋值,所以不需要同步访问。
最后,主线程在所有任务启动3秒后,调用Entrance的静态cancel()方法,之后再调用exec上的awaitTermination()方法。awaitTermination()等待每个任务结束,如果所有的任务在超时时间达到之前全部结束,返回true,否则返回false。
线程状态
- 新建:当线程被创建时,它只会短暂处于这种状态。此时它已被分配了必须的系统资源,并执行了初始化。此刻线程已有资格获得CPU时间了,之后调度器将整个线程转变为可运行状态或阻塞态。
- 就绪:在这种状态下,只要调度器把时间分片分配给线程,线程就可以运行。也就是说,任意时刻,线程可运行也可不运行。
- 阻塞:线程能够运行,但某个条件阻止它的运行。当线程处于阻塞状态,调度器将忽略线程,不会分配CPU时间给线程。直到线程重新进入就绪态,它才有可能被分配到CPU时间。
- 死亡:处于死亡或终止状态的线程将不再是可调度的,并且也不会得到CPU时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从run()方法返回,但是任务的线程还可以被中断。
- 通过调用sleep(milliseconds)使任务进入休眠状态,在这种情况下,任务在指定时间内是不会运行的。
- 通过wait()使线程挂起。直到线程得到notify()或notifyAll()的消息,线程才会进入就绪态
- 任务在等待某个输入/输入完成
- 任务视图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一哥任务已经获取了这个锁。
Thread类包含interrupt()方法,可以终止任务,这个方法将设置线程的终端状态,线程将抛出InterruptedException异常。
代码1-2
import java.util.concurrent.*;
import java.io.*;
class SleepBlocked implements Runnable {
public void run() {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
System.out.println("InterruptedException");
}
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 blocked 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.run()");
}
}
public class Interrupting {
private 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 Exception {
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);
}
}
代码1-2运行结果:
Interrupting SleepBlocked
Interrupt sent to SleepBlocked
InterruptedException
Exiting SleepBlocked.run()
Waiting for read():
Interrupting IOBlocked
Interrupt sent to IOBlocked
Trying to call f()
Interrupting SynchronizedBlocked
Interrupt sent to SynchronizedBlocked
Aborting with System.exit(0)
上面每一个任务都表示一种不同类型的阻塞。SleepBlocked是可中断的阻塞示例,而IOBlocked和SynchronizedBlocked是不可中断的阻塞示例。这两个程序分别证明I/O和在synchronized块上的等待是不可中断的。从输出可以看到,可以中断对sleep()的调用,但是不能中断试图获取synchronized锁或试图执行I/O的线程。
对于I/O异常无法中断问题,有一个笨拙但有效的方案,即关闭任务在其发生阻塞的底层资源
代码1-3
import java.net.*;
import java.util.concurrent.*;
import java.io.*;
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 blocked I/O");
} else {
throw new RuntimeException(e);
}
}
System.out.println("Exiting IOBlocked.run()");
}
}
public class CloseResource {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
ServerSocket server = new ServerSocket(8080);
InputStream socketInput = new Socket("localhost", 8080).getInputStream();
exec.execute(new IOBlocked(socketInput));
exec.execute(new IOBlocked(System.in));
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("Shutting down all threads");
exec.shutdownNow();
TimeUnit.SECONDS.sleep(1);
System.out.println("Closing " + socketInput.getClass().getName());
socketInput.close();
TimeUnit.SECONDS.sleep(1);
System.out.println("Closing " + System.in.getClass().getName());
System.in.close();
}
}
代码1-3运行结果(绿色为输入内容):
Waiting for read():
Waiting for read():
Shutting down all threads
Closing java.net.SocketInputStream
Interrupted from blocked I/O
Exiting IOBlocked.run()
Closing java.io.BufferedInputStream
Java Thread
Interrupted from blocked I/O
Exiting IOBlocked.run()
shutdownNow()被调用之后以及在两个输入流上调用close()之前的延迟强调的是一旦底层资源被关闭,任务将解除阻塞。
被阻塞的NIO类通道会自动地相应中断
代码1-4
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
class NIOBlocked implements Runnable {
private final SocketChannel sc;
public NIOBlocked(SocketChannel sc) {
super();
this.sc = sc;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
System.out.println("Waiting for read() in " + this);
sc.read(ByteBuffer.allocate(1));
} catch (ClosedByInterruptException e) {
// TODO Auto-generated catch block
System.out.println("ClosedByInterruptException");
} catch (AsynchronousCloseException e) {
// TODO Auto-generated catch block
System.out.println("AsynchronousCloseException");
} catch (IOException e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
System.out.println("Exiting NIOBlocked.run() " + this);
}
}
public class NIOInterruption {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
ServerSocket server = new ServerSocket(8080);
InetSocketAddress isa = new InetSocketAddress("localhost", 8080);
SocketChannel sc1 = SocketChannel.open(isa);
SocketChannel sc2 = SocketChannel.open(isa);
Future<?> f = exec.submit(new NIOBlocked(sc1));
exec.execute(new NIOBlocked(sc2));
exec.shutdown();
TimeUnit.SECONDS.sleep(1);
f.cancel(true);
TimeUnit.SECONDS.sleep(1);
sc2.close();
}
}
代码1-4运行结果:
Waiting for read() in com.jerry.demo2.NIOBlocked@7ad1e32d
Waiting for read() in com.jerry.demo2.NIOBlocked@674f1c67
ClosedByInterruptException
Exiting NIOBlocked.run() com.jerry.demo2.NIOBlocked@674f1c67
AsynchronousCloseException
Exiting NIOBlocked.run() com.jerry.demo2.NIOBlocked@7ad1e32d
被互斥锁阻塞
代码1-5
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
class BlockedMutex {
private Lock lock = new ReentrantLock();
public BlockedMutex() {
lock.lock();
}
public void f() {
try {
lock.lockInterruptibly();
System.out.println("lock acquired in f()");
} catch (InterruptedException e) {
System.out.println("Interrupted from lock acquisition in f()");
}
}
}
class Blocked2 implements Runnable {
BlockedMutex blocked = new BlockedMutex();
public void run() {
System.out.println("Waiting for f() in BlockedMutex");
blocked.f();
System.out.println("Broken out of blocked call");
}
}
public class Interrupting2 {
public static void main(String[] args) throws Exception {
Thread t = new Thread(new Blocked2());
t.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("Issuing t.interrupt()");
t.interrupt();
}
}
代码1-5运行结果:
Waiting for f() in BlockedMutex
Issuing t.interrupt()
Interrupted from lock acquisition in f()
Broken out of blocked call
BlockedMutex类有一个构造器,它要获取所创建对象自身上的Lock,并且不释放这个锁。出于这个原因,如果试图从第二个任务调用f(),那么将会因为BlockedMutex对象持有的锁而被阻塞。在Blocked2中,run()方法在调用blocked.f()的地方停止。与I/O不同,interrupt()可以打算被互斥阻塞的调用。
检查中断
通过调用interrupted()来检查中断状态
代码1-6
import java.util.concurrent.*;
class NeedsCleanup {
private final int id;
public NeedsCleanup(int ident) {
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;
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("Calculating");
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 {
public static void main(String[] args) throws Exception {
Thread t = new Thread(new Blocked3());
t.start();
TimeUnit.MILLISECONDS.sleep(3000);
t.interrupt();
}
}
代码1-6运行结果:
NeedsCleanup 1
Sleeping
NeedsCleanup 2
Calculating
Finished time-consuming operation
Cleaning up 2
Cleaning up 1
NeedsCleanup 1
Sleeping
NeedsCleanup 2
Calculating
Finished time-consuming operation
Cleaning up 2
Cleaning up 1
NeedsCleanup 1
Sleeping
Cleaning up 1
Exiting via InterruptedException