停止多线程的正确方法

原理介绍:

使用interrupt来通知,而不是强制

最佳实践: 如何正确停止线程

  1. 通常的停止过程(无外界干涉的情况下)
    1. run()方法执行完毕
    2. 有一点异常出现,但没有被捕获
  2. 正确方法: 用interrupt来请求停止线程
    1. 普通情况(run方法内没有sleep或wait方法的标准写法)
    2. 线程可能被阻塞
    3. 如果线程在每次工作迭代后都阻塞(调用sleep方法等)
    4. 如果不这样写,会遇到的问题: 线程无法停止
      1. while内try/catch的问题: java语言在设计sleep函数的时候的理念就是一旦中断,就是清除interrupt标记位
public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
                try {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
  1. 实际生产开发时要注意的编码习惯
    1. 两种最佳处理方式
      1. 优先选择: 传递中断,直接throw exception
      2. 不想或无法传递: 恢复中断, 通过Thread.currentThread().interrupt();
      3. 不应该屏蔽中断
  2. 可以为了响应中断而抛出interruptedException的常见方法列表总结
    1. Object.wait()/wait(long)/wait(long,int)
    2. Thread.sleep(long)/sleep(long,int)
    3. Thread.join()/join(long)/join(long,int)
    4. java.util.concurrent.BlockingQueue.take()/put(E)
    5. java.util.concurrent.locks.Lock.lockInterruptibly()
    6. java.util.concurrent.CountDownLatch.await()
    7. java.util.concurrent.Exchanger.exchange(V)
    8. java.util.concurrent.CyclicBarrier.await()
    9. java.nio.channels.InterruptibleChannel相关方法
    10. java.nio.channels.Selector的相关方法

错误的停止方法

  1. 被弃用的stop、suspend和resume方法
    1. 使用stop的后果
      • 用stop()来停止线程,会导致线程运行一半突然停止,没办法完成一个基本单位的操作(一个连队), 会造成脏数据(有的连队会多领取少领取装备)
    2. 关于stop的错误理论
      • 使用stop不能释放锁,这是错误的
    3. suspend的问题
      • suspend使线程暂停,但是不会释放类似锁这样的资源,时间久了会造成死锁
    4. resume: 使线程恢复,如果之前没有使用suspend暂停线程,则不起作用。
  2. 用volatile设置Boolean标记位
    • 看上去可行
    • 错误之处
      • 阻塞住了,没有去校验
    • 修正方式
      • 通过interrupt方法解决,而不是volatile
  3. 演示volatile失效方式
/**
 * 演示用volatile的局限 part2: 陷入阻塞时,volatile是无法停止线程的
 * 此例中,生产者的生产速度很快,消费者消费速度慢
 * 所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费
 */
public class WrongWayVolatileCantStop {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<Object> storage = new ArrayBlockingQueue<>(10);
        Producer producer = new Producer(storage);
        Thread thread = new Thread(producer);
        thread.start();
        Thread.sleep(1000);
        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了...");
        // 一旦消费者不需要更多数据,就应该让生产者停下来
        producer.canceled = true;

    }
}


class Producer implements Runnable {

    BlockingQueue storage;

    public volatile boolean canceled = false;

    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {

                if (num % 100 == 0) {
                    storage.put(num);
                    System.out.println(num + "是100的倍数。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生产者结束运行");
        }
    }
}

class Consumer {
    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    public boolean needMoreNums() {
        return Math.random() <= 0.95;
    }
}

停止线程相关的重要函数解析

  1. 中断线程
    1. interrupt方法原理分析
  2. 判断是否已经被中断
    1. static boolean interrupted()
    2. boolean isInterrupted()
    3. 举例说明,注意Thread.interrupted()的目的对象是“当前线程”,

常见面试问题

  1. 如何停止一个线程
    1. 原理: 用interrupt来请求、好处
    2. 想停止线程,要请求方、被停止方、子方法被调用方互相配合
    3. 最后再说错误的方法: stop/suspend已废弃,volatile的Boolean方法无法处理长时间阻塞的情况
  2. 如果处理不可中断的阻塞(例如抢锁时ReentrantLock.lock()或者Socket I/O时无法响应中断,那应该怎么让线程停止呢?)
上一篇:12.1


下一篇:python的数据结构整理-002-列表,字典,集合,怎么选?