一、通过条件判断退出任务
任务通过判断boolean值来确定何时终止它自己
实例代码:
public class OrnamentGarden{
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++){
service.execute(new Entrance(i));
}
TimeUnit.SECONDS.sleep(3);
Entrance.cancel();
service.shutdown();
if(!service.awaitTermination(10000, TimeUnit.MILLISECONDS)){
System.out.println("Some tasks were not terminated");
}
System.out.println("Total: " + Entrance.getTotalCount());
System.out.println("Sum of Entrances: " + Entrance.sumEntrance());
}
}
class Count {
private int count = 0;
private Random random = new Random();
public synchronized int increment(){
int temp = count;
if(random.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<>();
private int number = 0;
private static volatile boolean canceled = false;
private final int id;
public Entrance(int id){
this.id = id;
entrances.add(this);
}
public static void cancel(){
canceled = true;
}
@Override
public void run() {
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("Stoping " + this);
}
public synchronized int getValue(){
return number;
}
public static int getTotalCount(){
return count.value();
}
public static int sumEntrance(){
int sum = 0;
for (Entrance entrance: entrances){
sum += entrance.number;
}
return sum;
}
@Override
public String toString() {
return "Entrance " + id + " : " + getValue();
}
}
Entrance.canceled是一个volatile布尔标志,它只会被读取和赋值,所以不需要同步对其的访问,就可以安全的操作它。
二、中断
在Runnable.run方法的中间打断它,与等待该方法到达boolean标志或程序员准备离开该方法的其他一些地方相比,要棘手的多。当打断被阻塞的任务时,可能需要清理资源。
Thread类包含interrupt()方法,可以终止被阻塞的任务,这个方法将设置线程的中断状态。如果一个线程已经被阻塞,或者试图执行一个阻塞操作,那么设置这个线程的中断状态将抛出InterruptedException异常。当抛出该异常或者任务调用Thread.interrupted()时,中断状态将被复位。
如果在Executor上调用shutdownNow(),那么它将发送一个interrupt()调用给它启动的所有线程。当需要终止一个任务时,需要通过submit()而不是executor()启动任务,这样就可以持有该任务的上下文。submit()将返回一个泛型Future<?>,可以通过Future对象调用cannel(true)来终止某个特定的任务
注意:不能中断试图获取synchronized锁或者试图执行IO操作的线程
解决方案:
- IO阻塞解决方案
关闭任务在其上发生阻塞的底层资源 - 被互斥所阻塞解决方案
通过ReentrantLock类加锁,在ReentrantLock上阻塞的任务具备可以被中断的能力,ReentrantLock提供了lockInterruptibly()方法,
public class InterruptingReentranceLock {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Blocked());
thread.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("Issuing t.interrupt()");
thread.interrupt();
}
}
class BlockMutex{
private Lock lock = new ReentrantLock();
public BlockMutex(){
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 Blocked implements Runnable{
BlockMutex blockMutex = new BlockMutex();
@Override
public void run() {
System.out.println("waiting for f()");
blockMutex.f();
System.out.println("Broken out of blocked call");
}
}
三、检查中断
通过调用interrupted()来检查中断状态,这不仅可以告诉你interrupt()是否被调用过,而且还可以清除中断状态。清除中断状态可以确保并发结构不会就某个任务被中断这个问题通知你两次。
通过调用isInterrupted()检查中断状态,只会判断interrupt()是否被调用过,不会清除中断状态。