java多个任务不同时终止_[原创]Java并发编程(3):终止任务

使用共享变量来终止任务

比较简单的终止任务的方式是在任务执行时,判断一个标志位,如果满足一定条件,任务就终止。下面这个例子演示了这种简单的情形:

例子:使用共享变量来终止任务

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

importjava.util.ArrayList;importjava.util.List;importjava.util.Random;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.TimeUnit;classCount {private /*volatile*/ int count = 0;private Random rand = new Random(47);//Remove the synchronized keyword to see counting fail:

public synchronized intincrement() {int temp =count;if (rand.nextBoolean()) //Yield half the time

Thread.yield();return (count = ++temp);

}public synchronized intvalue() {returncount;

}

}class Entrance implementsRunnable {private static Count count = newCount();private static List entrances = new ArrayList();private int number = 0;//Doesn’t need synchronization to read:

private final intid;private static volatile boolean canceled = false;//Atomic operation on a volatile field:

public static voidcancel() {

canceled= true;

}public Entrance(intid) {this.id =id;//Keep this task in a list. Also prevents//garbage collection of dead tasks:

entrances.add(this);

}public voidrun() {while (!canceled) {synchronized (this) {++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 intgetValue() {returnnumber;

}publicString toString() {return "Entrance " + id + ": " +getValue();

}public static intgetTotalCount() {returncount.value();

}public static intsumEntrances() {int sum = 0;for(Entrance entrance : entrances)

sum+=entrance.getValue();returnsum;

}

}public classOrnamentalGarden {public static void main(String[] args) throwsException {

ExecutorService exec=Executors.newCachedThreadPool();for (int i = 0; i < 5; i++)

exec.execute(newEntrance(i));//Run for a while, then stop and collect the data:

TimeUnit.MILLISECONDS.sleep(100);

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());

}

}

输出:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

Entrance 1: 1 Total: 1Entrance3: 1 Total: 4Entrance0: 1 Total: 2Entrance4: 1 Total: 5Entrance2: 1 Total: 3Entrance1: 2 Total: 6Entrance4: 2 Total: 8Entrance0: 2 Total: 7Entrance3: 2 Total: 9Stopping Entrance2: 1Stopping Entrance0: 2Stopping Entrance4: 2Stopping Entrance1: 2Stopping Entrance3: 2Total:9Sum of Entrances:9

说明:

例子中模拟了为一个观赏植物公园计算进入园内游客数量的场景。公园有多个入口,每个入口都有一个计数器,另外有一个公共的计数器,每一个入口为自己的计数器加1的时候,同时也把公共计数器加1。最后判断各个入口计数器之和与公共计数器的数字是否一致。这个例子除了演示任务终止,同时也掩饰了共享资源的同步访问,以及volatile关键字的使用。

Count类用于模拟公共计数器,它有一个count成员,通过increment函数来对count执行递增操作。increment函数被synchronized关键字修饰,是受锁保护的:

public synchronized intincrement() {int temp =count;if (rand.nextBoolean()) //Yield half the time

Thread.yield();return (count = ++temp);

}

Entrance模拟公园的入口。它有一个static类型的类变量count,用于存储公共计数器。还有一个static类型的类变量List,用于把每一个入口实例都存起来,便于后续对每个入口的统计数字求和。number成员就是各个入口用于记录本入口统计数据的实例变量。还有一个static类型的并且被volatile修饰的类成员canceled,它就是共享变量,各个任务会判断这个变量来确定是否终止任务,不妨再看一下这个变量的定义:

private static volatile boolean canceled = false;

对canceled变量的修改是不依赖于其它值的(不管是它自己的旧值还是其它变量的值),也就是修改操作的过程中没有读操作,这是volatile修饰符能起到保持可见性和数据一致性的前提。Entrance类的run()函数中会判断canceled变量的值,如果改值为true,就退出任务:

public voidrun() {while (!canceled) {synchronized (this) {++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);

}

在这个例子中还需要重复几点说明:

canceled成员是多个任务共享的,必须用volatile修饰符来修饰它。并且对它的写操作不依赖与对其它变量(包括自己的旧值)的读操作,所以volatile修饰符是可以起到作用的。使用同步机制来控制对canceled成员的访问也是一种好办法。

count成员也是多任务共享的,而且它也是基本类型,但是对它的写操作必须使用synchronized来保护,而不能使用volatile变量。因为对count成员的写操作依赖于它的旧值。在increment()方法里面是先都去了count的值,存储到临时变量temp中,再对temp的值加1,然后赋值给count。

线程的四种状态

线程可以处于下面四种状态之一:

new

一个线程只会瞬间处于该状态。系统已经为该新生线程分配了必要的资源,并且完成了所有的初始化工作。线程调度器将会把这个线程转换为Runnable或者Blocked状态。

Runnable

这个状态意味着,线程处于可运行的状态,只要线程调度器为该线程分配了CPU时间,该线程就能够立即执行。处于该状态的线程可能正在运行,也可能正在等待分配CPU时间。

Blocked

线程因为某种原因(I/O操作,sleep)被阻塞了。线程调度器不会为该线程分配CPU时间,直到线程重新进入Runnable状态。

Dead

线程终止了,它不会再被调度,也就是不会再被分配CPU时间。终止的线程不可能再进入Runnable状态。线程终止的原因可以是从run()方法中返回,或者是线程被中断(interrupted)。

下面的图形描述了各状态之间的转换方式:

obg5NdxA0wMAAAAASUVORK5CYII=

一个线程在下面四种情况下会进入Blocked状态:

对线程调用sleep(milliseconds),线程会睡眠一段时间;

调用wait(),线程会进入Bolocked状态,直到notify()或notifyAll()的通知消息。

任务(线程)在等待I/O操作的完成

线程试图调用synchronized方法,而此时锁被其它线程持有。线程进入Blocked状态,直到获取锁。

备注:

过去曾经使用过的suspend(),resume()和stop()方法都被废弃了。stop()方法不会释放线程持有的锁,容易导致其它线程被锁死。不要使用这些方法。

中断

终止一个任务,就是给该任务发一个终止的信号。不同状态中的任务对于信号的处理是不同的,有下面三种情况:

线程处于Runnable状态,需要自己判断到这个信号,然后平稳的自行退出;

线程处于Blocked状态,并且这种阻塞是可中断阻塞,则会立即抛出一个InterruptedException,线程可以捕获到这个异常,做一些清理工作,而后退出;

线程处于Blocked状态,并且这种阻塞是不可中断阻塞,那么线程会对终止信号置之不理。也就是无法让处于不可中断阻塞状态的线程收到一个终止信号而退出。

可中断的阻塞包括:

sleep()调用

wait()调用

不可中断的阻塞

I/O操作

因为synchronized造成的阻塞

可以调用Executor的shutdownNow()方法来终止该Executor中的所有任务,实际上它是在Executor中所有线程上调用了Interrupt()方法。要想只终止Executor中的某一个线程,需要在提交任务的时候,调用execute.submit(),它会返回一个Future>对象,可以在这个对象上调用cancel()方法来终止这个任务。下面的例子演示了三种阻塞:I/O阻塞,sleep()阻塞,和synchronized阻塞:

例子:可中断阻塞和不可中断阻塞:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

importjava.io.IOException;importjava.io.InputStream;importjava.util.Date;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.Future;importjava.util.concurrent.TimeUnit;class SleepBlocked implementsRunnable {public voidrun() {try{

TimeUnit.SECONDS.sleep(100);

}catch(InterruptedException e) {

System.out.println(new Date() + ": " + "InterruptedException");

}

System.out.println(new Date() + ": " + "Exiting SleepBlocked.run()");

}

}class IOBlocked implementsRunnable {privateInputStream in;publicIOBlocked(InputStream is) {

in=is;

}public voidrun() {try{

System.out.println(new Date() + ": " + "Waiting for read():");

in.read();

}catch(IOException e) {if(Thread.currentThread().isInterrupted()) {

System.out.println(new Date() + ": " + "Interrupted from blocked I/O");

}else{throw newRuntimeException(e);

}

}

System.out.println(new Date() + ": " + "Exiting IOBlocked.run()");

}

}class SynchronizedBlocked implementsRunnable {public synchronized voidf() {while (true)//Never releases lock

Thread.yield();

}publicSynchronizedBlocked() {newThread() {public voidrun() {

f();//Lock acquired by this thread

}

}.start();

}public voidrun() {

System.out.println(new Date() + ": " + "Trying to call f()");

f();

System.out.println(new Date() + ": " + "Exiting SynchronizedBlocked.run()");

}

}public classInterrupting {private static ExecutorService exec =Executors.newCachedThreadPool();static void test(Runnable r) throwsInterruptedException {

Future> f =exec.submit(r);

TimeUnit.MILLISECONDS.sleep(100);

System.out.println(new Date() + ": " + "Interrupting " +r.getClass().getName());

f.cancel(true); //Interrupts if running

System.out.println(new Date() + ": " + "Interrupt sent to " +r.getClass().getName());

}public static void main(String[] args) throwsException {

test(newSleepBlocked());//test(new IOBlocked(System.in));//test(new SynchronizedBlocked());

TimeUnit.SECONDS.sleep(3);

System.out.println(new Date() + ": " + "Aborting with System.exit(0)");

System.exit(0); //... since last 2 interrupts failed

}

}

以此分别放开main()方法中的三条test()语句,对比一下不同的执行结果。执行test(new SleepBlocked());的结果是:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

Wed Feb 20 08:29:30 CST 2013: Interrupting com.quanquan.concurrency.interrupt.SleepBlocked

Wed Feb20 08:29:30 CST 2013: Interrupt sent to com.quanquan.concurrency.interrupt.SleepBlocked

Wed Feb20 08:29:30 CST 2013: InterruptedException

Wed Feb20 08:29:30 CST 2013: Exiting SleepBlocked.run()

Wed Feb20 08:29:33 CST 2013: Aborting with System.exit(0)

可以看到sleep()线程在收到Interrupt信号之后,立即退出了。执行test(new IOBlocked(System.in))的结果是:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

Wed Feb 20 08:33:21 CST 2013: Waiting forread():

Wed Feb20 08:33:21 CST 2013: Interrupting com.quanquan.concurrency.interrupt.IOBlocked

Wed Feb20 08:33:21 CST 2013: Interrupt sent to com.quanquan.concurrency.interrupt.IOBlocked

Wed Feb20 08:33:24 CST 2013: Aborting with System.exit(0)

可以看到 I/O阻塞的线程收到Interrupt信号之后,没有任何响应,直到整个程序退出。执行test(new SynchronizedBlocked());语句的结果是:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

Wed Feb 20 08:34:38 CST 2013: Trying to call f()

Wed Feb20 08:34:38 CST 2013: Interrupting com.quanquan.concurrency.interrupt.SynchronizedBlocked

Wed Feb20 08:34:38 CST 2013: Interrupt sent to com.quanquan.concurrency.interrupt.SynchronizedBlocked

Wed Feb20 08:34:41 CST 2013: Aborting with System.exit(0)

打破I/O阻塞

这个结果和I/O阻塞的结果是一样的,也没有对Interrupt信号做任何响应。

从上面的例子可以看出,应用程序可能会被I/O操作阻塞住。面对这种情况,可以通过关闭I/O资源来打破阻塞,因为关闭资源的时候,会导致I/O操作抛出IOException异常,如下面的例子:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

importjava.io.InputStream;importjava.net.ServerSocket;importjava.net.Socket;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.TimeUnit;public classCloseResource {public static void main(String[] args) throwsException {

ExecutorService exec=Executors.newCachedThreadPool();

ServerSocket server= new ServerSocket(8080);

InputStream socketInput= new Socket("localhost", 8080)

.getInputStream();

exec.execute(newIOBlocked(socketInput));

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();//Releases blocked thread

TimeUnit.SECONDS.sleep(1);

}

}

输出:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

Wed Feb 20 08:54:46 CST 2013: Waiting forread():

Shutting down all threads

Closing java.net.SocketInputStream

Wed Feb20 08:54:47 CST 2013: Interrupted from blocked I/O

Wed Feb20 08:54:47 CST 2013: Exiting IOBlocked.run()

可以看到在关闭socket连接之后,阻塞的I/O线程抛出了IOException异常,I/O线程的代码是这样的:

try{

System.out.println(new Date() + ": " + "Waiting for read():");

in.read();

}catch(IOException e) {if(Thread.currentThread().isInterrupted()) {

System.out.println(new Date() + ": " + "Interrupted from blocked I/O");

}else{throw newRuntimeException(e);

}

}

可中断的“互斥量”阻塞(ReentrantLock锁)

Java SE5的coccurrency库中新增了ReentrantLock,它的特别之处在于被这种锁阻塞的任务是可以中断的。请看下面的例子:

例子:ReentrantLock锁

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;classBlockedMutex {private Lock lock = newReentrantLock();publicBlockedMutex() {//Acquire it right away, to demonstrate interruption//of a task blocked on a ReentrantLock:

lock.lock();

}public voidf() {try{//This will never be available to a second task

lock.lockInterruptibly(); //Special call

System.out.println("lock acquired in f()");

}catch(InterruptedException e) {

System.out.println("Interrupted from lock acquisition in f()");

}

}

}class Blocked2 implementsRunnable {

BlockedMutex blocked= newBlockedMutex();public voidrun() {

System.out.println("Waiting for f() in BlockedMutex");

blocked.f();

System.out.println("Broken out of blocked call");

}

}public classInterrupting2 {public static void main(String[] args) throwsException {

Thread t= new Thread(newBlocked2());

t.start();

TimeUnit.SECONDS.sleep(1);

System.out.println("Issuing t.interrupt()");

t.interrupt();

}

}

输出:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

Waiting forf() in BlockedMutex

Issuing t.interrupt()

Interrupted from lock acquisition in f()

Broken out of blocked call

说明:

例子中,BlockedMutex对象在构造函数里获取了ReentrantLock锁,所以f()函数会被阻塞,f()函数调用的是lock.lockInterruptibly();这个函数在收到Interrupt信号之后,会退出阻塞状态,并抛出InterruptedException异常。

检查中断状态

一个不会处于阻塞状态的任务,在收到interrupt信号之后,不会抛出InterruptedException异常,但是其中断状态被设置,可以通过调用Thread.interrupted( ) 来判断中断状态,以便在收到interrupt信号之后退出任务。下面的例子演示了这一点。

例子:判断中断状态

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

importjava.util.concurrent.TimeUnit;classNeedsCleanup {private final intid;public NeedsCleanup(intident) {

id=ident;

System.out.println("NeedsCleanup " +id);

}public voidcleanup() {

System.out.println("Cleaning up " +id);

}

}class Blocked3 implementsRunnable {private volatile double d = 0.0;public voidrun() {try{while (!Thread.interrupted()) {//point1

NeedsCleanup n1 = new NeedsCleanup(1);//Start try-finally immediately after definition//of n1, to guarantee proper cleanup of n1:

try{

System.out.println("Sleeping");

TimeUnit.MILLISECONDS.sleep(900);//point2

NeedsCleanup n2 = new NeedsCleanup(2);//Guarantee proper cleanup of n2:

try{

System.out.println("Calculating");//A time-consuming, non-blocking operation:

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");

}finally{

;

}

}

}public classInterruptingIdiom {public static void main(String[] args) throwsException {if (args.length != 1) {

System.out.println("usage: java InterruptingIdiom delay-in-mS");

System.exit(1);

}

Thread t= new Thread(newBlocked3());

t.start();

TimeUnit.MILLISECONDS.sleep(new Integer(args[0]));

t.interrupt();

}

}

输出:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngView Code

NeedsCleanup 1Sleeping

NeedsCleanup2Calculating

Finished time-consuming operation

Cleaning up2Cleaning up1NeedsCleanup1Sleeping

Cleaning up1Exiting via InterruptedException

说明:

Blocked3是一个任务,在run()函数里面,while循环在每次循环开始的时候,调用Thread.interrupted()来判断线程的中断状态是否为true,如果没有被中断,就初始化一个NeedsCleanup对象,并立即在一个try{sleep()}finally{}的finally子句中调用n1.cleanup()函数,做必要的清理工作,以保证sleep()调用被中断的时候,可以完成清理工作。从代码中注释的point2位置开始,都是不会中断的代码,所以n2的清理工作总能够完成。这个例子清楚的演示了,如何让任务在合适的时机终止,并做好最后的清理工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值