• java并发编程(1)并发程序的取消于关闭


    一、任务的取消于关闭

    1、中断Thread

      1.每个线程都有一个boolean类型的中断状态。true则是中断状态中

        interrupt:发出中断请求;isInterrupt:返回中断状态;interrupted:清除中断状态

      2.JVM中的阻塞方法会检查线程中断状态,其响应方法为:清除中断状态,抛出InterruptedException异常,表示阻塞操作被中断结束 ;但JVM不保证阻塞方法何时检测到线程的中断状态

      3.中断的理解:不会真正的中断一个正在运行的线程,而只是发出请求,具体的中断由任务自己处理

      通过中断来取消线程通常是最好的方法

    public class PrimeProducer extends Thread {
        private final BlockingQueue<BigInteger> queue;
        PrimeProducer(BlockingQueue<BigInteger> queue) {
            this.queue = queue;
        }
        public void run() {
            try {
                BigInteger p = BigInteger.ONE;
                while (!Thread.currentThread().isInterrupted())
                    queue.put(p = p.nextProbablePrime());
            } catch (InterruptedException consumed) {
                /* Allow thread to exit */
                //如果捕获到中断异常,则由线程自己退出
            }
        }
        public void cancel() {
            interrupt();
        }
    }

    2、不可中断的阻塞的中断

      如:Socket I/O操作,即使设置了中断请求,也不会中断,但是close 套接字,会使其抛出异常,达到中断效果;因此我们要重写中断方法 

      

    //自定义callable实现类
    public abstract class SocketUsingTask <T> implements CancellableTask<T> {
        private Socket socket;
    
        protected synchronized void setSocket(Socket s) {
            socket = s;
        }
        //取消方法
        public synchronized void cancel() {
            try {
                if (socket != null)
                    socket.close();
            } catch (IOException ignored) {
            }
        }
        //新建实例的方法
        public RunnableFuture<T> newTask() {
            return new FutureTask<T>(this) {
                public boolean cancel(boolean mayInterruptIfRunning) {
                    try {
                        SocketUsingTask.this.cancel();
                    } finally {
                        return super.cancel(mayInterruptIfRunning);
                    }
                }
            };
        }
    }
    
    //自定义callable接口
    interface CancellableTask <T> extends Callable<T> {
        void cancel();
        RunnableFuture<T> newTask();
    }
    //自定义 执行池
    class CancellingExecutor extends ThreadPoolExecutor {
        ......
        //通过改写newTaskFor 返回自己的Callable
        protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
            if (callable instanceof CancellableTask)
                return ((CancellableTask<T>) callable).newTask();
            else
                return super.newTaskFor(callable);
        }
    }

    3、通过自定义取消计时任务

    private static final ScheduledExecutorService cancelExec = newScheduledThreadPool(1);
        /**
         *
         * @param r 任务
         * @param timeout 超时时间
         * @param unit TimeUnit
         * @throws InterruptedException
         */
        public static void timedRun(final Runnable r,long timeout, TimeUnit unit) throws InterruptedException {
            class RethrowableTask implements Runnable {
                //通过一个volatile变量,来存储线程是否异常
                private volatile Throwable t;
                public void run() {
                    try {
                        r.run();
                    } catch (Throwable t) {
                        this.t = t;
                    }
                }
                private void rethrow() {
                    if (t != null)
                        throw launderThrowable(t);
                }
            }
            RethrowableTask task = new RethrowableTask();
            final Thread taskThread = new Thread(task);
            taskThread.start();
            //延时timeout个unit单位后 执行线程中断
            cancelExec.schedule(() -> taskThread.interrupt(), timeout, unit);
            //无论如何都等待;如果线程不响应中断,那么通过join等待任务线程timeout时间后 不再等待,回到调用者线程
            taskThread.join(unit.toMillis(timeout));
            //如果 任务线程中有异常,则抛出
            task.rethrow();
        }

    注意:依赖于join,任务超时join退出 和 任务正常join推出 无法进行判断

     4、通过Futrue来实现取消计时任务

    private static final ExecutorService taskExec = Executors.newCachedThreadPool();
        public static void timedRun(Runnable r,long timeout, TimeUnit unit) throws InterruptedException {
            Future<?> task = taskExec.submit(r);
            try {
                //通过Futrue.get(超时时间),捕获相应的异常来处理计时运行和取消任务
                task.get(timeout, unit);
            } catch (TimeoutException e) {
                // task will be cancelled below
            } catch (ExecutionException e) {
                // exception thrown in task; rethrow
                throw launderThrowable(e.getCause());
            } finally {
                // Harmless if task already completed
                task.cancel(true); // interrupt if running
            }
        }

     二、停止基于线程的服务

      1.通常,服务不能直接中断,造成服务数据丢失

      2.线程池服务也不能直接中断

    1、日志服务

    标准的生产者,消费者模式

    public class LogService {
        private final BlockingQueue<String> queue;
        private final LoggerThread loggerThread;
        private final PrintWriter writer;
        private boolean isShutdown;
        private int reservations;
    
        public LogService(Writer writer) {
            this.queue = new LinkedBlockingQueue<String>();
            this.loggerThread = new LoggerThread();
            this.writer = new PrintWriter(writer);
        }
    
        public void start() {
            loggerThread.start();
        }
    
        public void stop() {
            synchronized (this) {
                isShutdown = true;
            }
            loggerThread.interrupt();   //发出中断
        }
    
        public void log(String msg) throws InterruptedException {
            synchronized (this) {
                if (isShutdown){
                    throw new IllegalStateException(/*...*/);
                }
                ++reservations; //保存的正确的在队列中的日志数量
            }
            queue.put(msg);     //将日志放入队列
        }
    
        private class LoggerThread extends Thread {
            public void run() {
                try {
                    while (true) {
                        try {
                            synchronized (LogService.this) {
                                if (isShutdown && reservations == 0) {
                                    break;
                                }
                            }
                            String msg = queue.take();
                            synchronized (LogService.this) {
                                --reservations;
                            }
                            writer.println(msg);
                        } catch (InterruptedException e) { /* retry */
                            //捕获了中断请求,但为了将剩余日志输出,不做处理,直到计数器 == 0时,关闭
                        }
                    }
                } finally {
                    writer.close();
                }
            }
        }
    }

    2、ExecutorService中断

      shutDown和shutDownNow

      通常,将ExecetorService封装;如LogService,使其具有自己的生命周期方法

      shutDownNow的局限性:不知道当前池中的线程状态,返回未开始的任务,但不能返回已开始未结束的任务

      

    public class TrackingExecutor extends AbstractExecutorService {
        private final ExecutorService exec;
        private final Set<Runnable> tasksCancelledAtShutdown =
                Collections.synchronizedSet(new HashSet<Runnable>());
    
        public TrackingExecutor() {
            exec = Executors.newSingleThreadExecutor();
        }
    
        /*public TrackingExecutor(ExecutorService exec) {
            this.exec = exec;
        }*/
    
        public void shutdown() {
            exec.shutdown();
        }
    
        public List<Runnable> shutdownNow() {
            return exec.shutdownNow();
        }
    
        public boolean isShutdown() {
            return exec.isShutdown();
        }
    
        public boolean isTerminated() {
            return exec.isTerminated();
        }
    
        public boolean awaitTermination(long timeout, TimeUnit unit)
                throws InterruptedException {
            return exec.awaitTermination(timeout, unit);
        }
    
        public List<Runnable> getCancelledTasks() {
            if (!exec.isTerminated())
                throw new IllegalStateException(/*...*/);
            return new ArrayList<Runnable>(tasksCancelledAtShutdown);
        }
    
        public void execute(final Runnable runnable) {
            exec.execute(new Runnable() {
                public void run() {
                    try {
                        runnable.run();
                    } finally {
                        if (isShutdown()
                                && Thread.currentThread().isInterrupted())
                            tasksCancelledAtShutdown.add(runnable);
                    }
                }
            });
        }
    
        @Test
        public void test() throws InterruptedException {
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            TrackingExecutor trackingExecutor = new TrackingExecutor();
            trackingExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                        System.err.println("123123");
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt(); //设置状态 或继续抛,在execute中处理
                        e.printStackTrace();
                    } finally {
    
                    }
                }
            });
            List<Runnable> runnables = trackingExecutor.shutdownNow();
            trackingExecutor.awaitTermination(10,TimeUnit.SECONDS);
            List<Runnable> cancelledTasks = trackingExecutor.getCancelledTasks();
            System.err.println(cancelledTasks.size());
        }
    }

    三、处理非正常线程终止

    1.未捕获的Exception导致的线程终止

      1.手动处理未捕获的异常

      2.通过Thread的API UncaughExceptionHandler,能检测出某个线程又遇见未捕获而导致异常终止

        注意:默认是将异常的的堆栈信息 输出到控制台;自定义的Handler:implements Thread.UncaughExceptionHandler覆写方法

        可以为每个线程设置,也可以设置一个全局的ThreadGroup

        Thread.setUncaughtExceptionHandler/Thread.setDefaultUncaughtExceptionHandler

    2.JVM退出、守护线程等

      

      

  • 相关阅读:
    ABP框架(asp.net core 2.X+Vue)运行前端(转)
    Spring学习指南第3版例子程序导入idea20.02
    Docker运行jar包(转)
    centos7使用docker搭建运行环境并部署jar(转)
    centos7修改docker镜像源的方法(转)
    虚拟机下CentOS7安装Docker(转)
    Virtual Box配置CentOS7网络(图文教程)(转)
    虚拟机docker运行jar程序
    docker安装
    【591】通过 Terminal 直接获取 GitHub 源码
  • 原文地址:https://www.cnblogs.com/zhangxinly/p/6905983.html
Copyright © 2020-2023  润新知