java并发编程基础(三)-终结任务

java并发编程基础(三)-终结任务

本文为学习《thinking in java》第21章的相关笔记

线程结束任务

  1. 样例

    class Worker implements Runnable{
        private static volatile boolean canceled = false; // static静态使得多个线程都共享
        public static void cancel(){ // 设置停止标记
            canceled = true;
        }
        
        @Override
        public void run() {
            while (!canceled) {
                // do something
            }
        }
    }
    

    使用ExecutorService启动任务后,当我们想停止线程的执行时,就可以使用以下语句

    Worker.cancel();
    executorService.shutdown();
    

阻塞

  1. 忙等待和阻塞
    • 前者占用CPU,后者不占用
  2. 线程进入阻塞状态
    • 调用sleep方法
    • 调用wait方法
    • 任务等待IO完成
    • 任务试图获取对象锁,但是对象锁不可用

中断

  1. Thread.interrupt()

  2. Executer.shutdownNow()

    • shutdown(): 线程池状态变为SHUTDOWN状态,不再往线程池中添加任务,但是线程池不会立刻退出,直到之前的任务处理完成才会退出
    • shutdownNow(): 线程池立刻变为STOP状态,并试图停止所有正在执行的线程,不再不处理还在线程池中等待的任务。其实是通过Thread.interupt来实现的。但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
    • 上述解释都只是参考网上博文,并不是书本内容
  3. 有时候你希望只是停止一个任务,可以使用submit()而不是execute()来启动任务,submit()返回泛型Future,可以调用cancel()来中断该任务。如果将true传递给cancel(),那么它就会拥有在该线程上调用interrupt的权限

  4. 经过书本实验证明,IO和在synchronized块上的等待都是不可中断的,而sleep()的方法是可以中断的。但是这种IO阻塞的问题,只可以通过关闭任务在其发生阻塞的底层资源,比如一个监听ServerSocket的InputStream的线程,可以通过关闭这个InputStream来中断线程的阻塞

  5. 幸运的是,被阻塞的nio通道会自动响应中断。当然,同理,你可以可以直接关闭底层资源。

被互斥所阻塞

  1. 同一个互斥可以被同一个任务多次获得

    public class MultiLock {
    
        public synchronized void f1(int count){
            if (count-- > 0){
                System.out.println("f1 is ready to call f2 with the curr count is " + count);
                f2(count);
            }
        }
    
        public synchronized void f2(int count) {
            if (count-- > 0){
                System.out.println("f2 is ready to call f1 with the curr count is " + count);
                f1(count);
            }
        }
    
        public static void main(String[] args) {
            final MultiLock multiLock = new MultiLock();
            new Thread(){
                @Override
                public void run() {
                    multiLock.f1(10);
                }
            }.start();
        }
    }
    

    只要是在同一个任务获得对象,f1()和f2()就不会互斥

  2. 在ReentranLock上阻塞的任务具备可以被中断的能力,这和在synchronized方法上或者临界区上阻塞的任务完全不同

    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 blockedMutex = new BlockedMutex();
    
        @Override
        public void run() {
            System.out.println("waiting for f() in BlockedMutex");
            blockedMutex.f();
            System.out.println("Broken out of blocked call");
        }
    }
    
    public class Interrupting2 {
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Blocked2());
            thread.start();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Issuing thread.interrupted()");
            thread.interrupt();
        }
    }
    
    

    BlockedMutex类有一个构造器,它获取创建对象上自身的lock且不释放它。如果试图从第二个任务中调用f(),那么将会是因为Mutex不可获得而被阻塞。故在Blocked2中,run方法总是在调用blockedMutex.f()时被阻塞。但是本次程序使用的是lock.lockInterruptbly(), 它是可以响应中断的。这正是lock()和lockInterruptbly()区别

    • lock()优先获得锁,先获得锁再响应中断
    • lockInterruptbly()优先考虑响应中断

检查中断

  1. 在线程上调用interrupt()时,中断发生的唯一时刻是任务要进入到阻塞操作中,或者已经在阻塞操作内部(除了不可中断的IO和synchronized方法外)

  2. 可以通过调用interrupted()来检查中断状态,不仅可以知道interrupt是否被调用过,而且还可以清除中断状态

    class NeedsCleanup{
    
        private final int id;
    
        public NeedsCleanup(int id) {
            this.id = id;
            System.out.println("Needs clean up " + id);
        }
    
        public void cleanup(){
            System.out.println("cleaning up " + id);
        }
    }
    
    class Blocked3 implements Runnable {
    
        private volatile double d = 0.0;
        
        @Override
        public void run() {
            try {
                while (!Thread.interrupted()) {
                    // point 1
                    NeedsCleanup n1 = new NeedsCleanup(1);
    
                    try {
                        System.out.println("sleeping");
    
                        TimeUnit.SECONDS.sleep(1);
    
                        //point 2
                        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 InterruptedException {
            Thread thread = new Thread(new Blocked3());
            thread.start();
            TimeUnit.MILLISECONDS.sleep(1050); // 调值
            thread.interrupt();
        }
    }
    

    尝试运行上述代码,并在我注释调值处尝试不同的睡眠时间,可以发现

    • 如果Interrupt()的调用在注释point2之后,即在非阻塞的操作过程中被调用,那么会首先继续计算直到计算结束,n1和n2调用完cleanup(),然后在while的顶部退出循环
    • 如果Interrupt()的调用在注释point1和point2之间(在while语句之后,但在sleep()之前或者其过程中)被调用,那么任务就会经由InterruptedException退出

你可能感兴趣的:(java)