Java Concurrent Programming Practical Notes (Chapter 7-Cancellation and Close)

It is not easy to make tasks and threads stop safely. If a task, thread, or service is stopped immediately, the shared data structure will be in an inconsistent state . When it is necessary to stop, they will first clear the work currently being performed, and then end.

1. Task cancellation

If the external code can put an operation into the "completed" state before it completes normally , then the operation is said to be cancelable .

Reasons for canceling an operation:

  • User request to cancel
  • Time-limited operation: timer expired
  • Application event
  • Error: runtime error
  • Close: When the program or service is closed 

Set a certain "cancel requested" flag, and the task periodically checks this flag, if it is canceled, the task will end early.

package chapter6;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

public class PrimeGenerator implements Runnable{
    private final List<BigInteger> primes = new ArrayList<BigInteger>();
    // 取消的标志
    private volatile boolean cancelled;

    public void run(){
        BigInteger p = BigInteger.ONE;
        System.out.println(Thread.currentThread().getName());
        while(!cancelled){

            p = p.nextProbablePrime();
            synchronized (this){
                primes.add(p);
            }
        }
    }

    public void cancel(){
        cancelled = true;
    }

    public synchronized List<BigInteger> get(){
        return new ArrayList<BigInteger>(primes);
    }

    public static void main(String[] args) throws InterruptedException{
        PrimeGenerator primeGenerator = new PrimeGenerator();

        Thread thread = new Thread(primeGenerator);
        System.out.println(Thread.currentThread().getName());
        thread.start();
        Thread.sleep(3000);
        primeGenerator.cancel();

        System.out.println(primeGenerator.get());
    }
}

A cancelable task must have a cancellation strategy: that is, how other codes ( How ) request cancellation of the task, when ( When ) the task checks whether cancellation has been requested, and those ( what ) operations should be performed in response to the cancellation request .

Interrupt

There is a problem with the above code: if a blocking method (BlockingQueue.put) is called , the task may never check the cancellation flag and therefore never end.

import java.math.BigInteger;
import java.util.concurrent.BlockingQueue;

public class BrokenPrimeProducer extends Thread {
    private final BlockingQueue<BigInteger> queue;
    private volatile boolean cancelled = false;

    public BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try{
            BigInteger p = BigInteger.ONE;
            while(!cancelled){
                queue.put(p = p.nextProbablePrime());
            }
        } catch (InterruptedException e){

        }
    }

    public void cancel(){
        this.cancelled = true;
    }

    void consumePrimes() throws InterruptedException{
        BlockingQueue<BigInteger> primes =;
        BrokenPrimeProducer producer = new BrokenPrimeProducer(primes);
        producer.start();
        try{
            while(needMorePrimes())
                consume(primes.take());
        }finally {
            producer.cancel();
        }
    }
}

The interrupt method in Thread:

public class Thread{
    /*中断目标线程*/
    public void interrupt(){...}

    /*f返回目标线程的中断状态*/
    public boolean isInterrupted(){...}

    /*清除当前线程的中断状态*/
    public static boolean interrupted(){...}
    ...
}


Thread.sleep()和Object.wait()等方法都会检查合适中断,并且发现中断时返回。
他们在响应中断时执行的操作包括:清除中断状态,抛出InterruptedException异常,
表示阻塞操作由于中断提前结束。
try{}catch(InterruptedException e){}

 

 Interruption strategy

The most reasonable interruption strategy is some form of thread-level cancellation operation or service-level cancellation operation: exit as soon as possible, clean up when necessary, and notify an owner that the thread has exited.

It is important to distinguish the response of tasks and threads to interrupts. An interrupt request can have one or more receivers---interrupt a worker thread in the thread pool, which means " cancel the current task " and " close the worker thread " at the same time . Most blockable library functions just throw InterruptedException as an interrupt response. Because they implement the most reasonable cancellation strategy: exit the execution process as soon as possible, and pass the interrupt information to the caller, so that the upper code in the call stack can take further actions.

Since each thread has its own interrupt strategy , you should not interrupt this thread unless you know the meaning of interruption to the thread.

Respond to interrupt

When calling interruptible blocking functions (Thread.sleep, BlockingQueue.put, etc.), there are two strategies to handle InterruptedException:

  1. Pass the exception so that your method also becomes an interruptible method.
  2. Restore the interrupted state , so that the upper code in the call stack can handle it.
// 传递InterruptedException与将InterruptedException添加到throws子句一样简单
BlockingQueue<Task> queue;
...
public Task getNextTask() throws InterruptedException {
    return queue.take();
}

If you do not want or cannot deliver InterruptedException, you need to find another way to save the interrupt request. A standard way is to restore the interrupted state by calling interrupt again [InterruptedException cannot be shielded]. Only the code that implements the thread interrupt strategy can shield the interrupt request. In normal tasks and code, interrupt requests should not be masked.

package chapter7;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * 〈一句话功能简述〉<br> 
 * 〈〉
 *
 * @author 我们
 * @create 2021/1/22
 * @since 1.0.0
 */
public class InterruptedExceptionDemo {
    public static Integer getNextTask(BlockingQueue<Integer> queue){
        boolean interrupted = false;
        try{
            while(true){
                try{
                    return queue.take();
                } catch (InterruptedException e){
                    interrupted = true;
                    // 重新尝试
                }
            }
        } finally {
            if (interrupted){
                Thread.currentThread().interrupt(); // 再次调用interrupt()恢复中断
            }
        }
    }
    public static void main(String[] args) throws InterruptedExcepiton{
        BlockingQueue<Integer> queue = new LinkedBlockingDeque<>();

        queue.add(1);
        queue.add(2);
        queue.add(3);
        queue.add(4);

        System.out.println(getNextTask(queue));
        System.out.println(getNextTask(queue));
        System.out.println(getNextTask(queue));

    }
}

Example: Timed run

private static final SchedulExecutorService cancelExec = ...;

// 如果任务在超时之前完成,那么中断timedRun所在线程的取消任务将在timedRun返回到调用者之后启动,
// 如果任务不响应中断,那么timedRun会在任务结束时返回,此时已经超过了指定的时限
public static void timedRun (Runnable r, long timeout, TimeUnit unit){
    final Thread taskThread = Thread.currentThread();
    cancelExec.schedule(new Runnable(){
    @Override        
    public void run{
        // 在中断线程之前,应该了解它的中断策略。由于timeRun()可以被任意一个线程调用,因此无法
        // 知道这个调用线程的中断策略(处理InterruptedException的方法)
        taskThread.interrupt();
    },  timeout, unit);
    r.run();
}
           

 It relies on a time-limited join, so there is insufficient join: it is impossible to know whether the execution control returns because the thread exits normally or because the join timeout returns.

public static void timedRun(final Runnable r, long timeout, TimeUnit unit)
    throws InterruptedException{

    // 用RethrowableTask来捕获r执行过程中的异常
    class RethrowableTask implements Runnable{
        // 由于Throwable将在两个线程之间共享,所以声明为volatile,
        // 确保任务线程InterruptedException发布到timedRun中
        private volatile Throwable t; 
        public void run(){
            try{    r.run(); }
            catch (Throwable t) {    this.t = t; }
        }
        void rethrow(){
            if (t != null){
                throw launderThrowable(t);
            }
    }
    RethrowableTask task = new RethrowableTask();
    final Thread taskThread = new Thread(task);
    taskThread.start(); // 执行任务
    cancelExec.schedule(new Runnable(){
        public void run() {    taskThread.interrupt(); }
    }, timeout, unit);
    // 在当前线程中调用另外一个线程的join方法:则当前线程转为阻塞状态,等待调用join线程结束
    taskThread.join(unit.toMillis(timeout)); // taskThread等待Runnable任务结束
    task.rethrow(); // 抛出异常
}

Cancellation through Future

We have used an abstract mechanism to manage the life cycle of tasks, handle exceptions, and achieve cancellation, namely Future.

Future future = ExecutorService.submit();
boolean cancel(boolean mayInterruptIfRunning);
mayInterruptIfRunning: Indicates whether the cancellation operation was successful. 
1) True and the task is running in a thread, then this thread can be interrupted. 
2) false: If the task has not been started, don't run it. 

Under what circumstances can you call cancel to specify the parameter as true? 
Created by the standard Executor, it implements an interrupt strategy so that tasks can be canceled through interruption, so if tasks are running in standard Executor, they can be canceled through their Future Task, then you can set true, when trying to cancel a task, it is not appropriate to directly interrupt the thread pool.
public static void timedRun(Runnable r, long timeout, TimeUnit unit)
    throws InterruptedException{
    Future<?> task = taskExec.submit(r); // 用线程池执行任务
    try{
        task.get(timeout, unit);  // 设置超时时间
    } catch (TimeoutException e){
        // 接下来任务将被取消
    } catch (ExecutionException e){
        // 如果在任务中抛出异常,那么重新抛出该异常
        throw launderThrowable(e.getCause());
    } finally {
        // !!!如果任务已经结束,那么执行取消操作也不会带来任何影响!!!
        task.cancel(true);     // 如果任务正在运行,那么将被中断
    }
}

When Future.get() throws InterruptedException or TimeoutException, if you know that the result is no longer needed, you can call Future.cancel() to cancel the task.

Guess you like

Origin blog.csdn.net/weixin_39443483/article/details/112910175