找回密码
 立即注册
首页 编程领域 编程板块 聊一聊如何优雅的停下线程,除了这两种还有其他的吗?

Java 聊一聊如何优雅的停下线程,除了这两种还有其他的吗?

2023-3-23 14:25:36 评论(0)
本帖最后由 西西子 于 2023-3-23 14:32 编辑


前言
今天主要来聊一聊如何优雅的停下线程。
在开始之前,我们可以思考一下,如何能够让线程停下?
通过查阅JDK,我们不难发现Thread为我们提供了一个stop方法,只要使用stop方法,就立即停止线程,但是发现stop()方法被标注为废弃的方法,因为这个方法会强行把执行到一半的线程终止,可能会引发一些数据不一致或者我们没发预估的问题。
除了stop()方法,我能想到的方案还有两个,
方案一:使用volatile标记位,利用其可见性
方案二:调用Thread的方法interrupted

方案一:使用volatile标记位,利用其可见性
通过代码我们来看下方案一,这是一个很经典的生产者和消费者模式。
生产者Demo
  1. //生产者
  2. class Producer implements Runnable {
  3.     public volatile boolean canc = false;

  4.     private Product product;

  5.     Producer(Product product) {
  6.         this.product = product;
  7.     }

  8.     @Override
  9.     public void run() {
  10.         try {
  11.             while (!canc) {
  12.                 try {
  13.                     //Thread.sleep(1000);
  14.                     product.put("iphone6s");
  15.                     System.out.println("put:" + Thread.currentThread().getName());
  16.                 } catch (InterruptedException e) {
  17.                     e.printStackTrace();
  18.                 }
  19.             }
  20.         } catch (Exception ex) {
  21.             ...
  22.         } finally {
  23.             System.out.println("结束");
  24.         }
  25.     }
  26. }
复制代码
消费者Demo
  1. //消费者
  2. class Consumer implements  Runnable{
  3.     private Product product;

  4.     Consumer(Product product) {
  5.         this.product = product;
  6.     }

  7.     @Override
  8.     public void run() {
  9.         while (Math.random() > 0.9){
  10.             try {
  11.                 Thread.sleep(1000);
  12.                 product.take("iPhone6s");
  13.                 System.out.println("take:"+Thread.currentThread().getName());
  14.             } catch (InterruptedException e) {
  15.                 e.printStackTrace();
  16.             }
  17.         }
  18.     }
  19. }
复制代码
调用生产者和消费者
  1. public static void main(String[] args) {
  2.     ArrayBlockingQueue queue = new ArrayBlockingQueue(1);
  3.     Product product = new Product(queue);
  4.     Producer producer = new Producer(product);
  5.     Consumer consumer = new Consumer(product);
  6.     Thread c1 = new Thread(consumer);
  7.     Thread p1 = new Thread(producer);
  8.     p1.start();
  9.     try {
  10.         Thread.sleep(500);
  11.     } catch (InterruptedException e) {
  12.         e.printStackTrace();
  13.     }
  14.     c1.start();
  15.     System.out.println("消费者不需要更多数据了。");
  16.     producer.canc = true;
  17.     System.out.println(producer.canc);
  18.     System.out.println(p1.getState());
  19. }
复制代码

场景一: 我们把消费者和生产者的线程都开起来,生产者生产一个产品,消费者都会消费一个产品,这个时候volatile的值,在下一次的轮询中值已经变成了true,就跳出while循环,线程就停止,这个场景下volatile就适用了。
场景二: 我们将消费者线程不启动,只生产不消费。 理论上我们期待的结果应该也是值变成true,跳出while循环,线程停止。
结果打印:
  1. Put a iphone6s
  2. put:Thread-2
  3. 消费者不需要更多数据了。
  4. valatile的值: true
  5. 线程状态:WAITING
复制代码
根据打印的结果我们会观察到他没有输出结束的语句,
我们看到了生产者生产了产品,valatile也修改了值,但是线程却没有结束,
这主要的原因是因为,生产者执行了product.put("iphone6s"),没有被消费,造成了阻塞,在它唤醒之前,
无法进入下一次的轮询判断。造成了值修改了,却没有做出相应处理。
我们发现在消费的时候,take方法内部会触发唤醒,当检测到线程已经停止,则抛出InterruptedException异常。


开源码说话,可以看到dequeue,唤醒了线程。
  1. public E take() throws InterruptedException {
  2.     final ReentrantLock lock = this.lock;
  3.     lock.lockInterruptibly();
  4.     try {
  5.         while (count == 0)
  6.             notEmpty.await();
  7.         return dequeue();
  8.     } finally {
  9.         lock.unlock();
  10.     }
  11. }

  12. public final void acquireInterruptibly(int arg)
  13.         throws InterruptedException {
  14.     if (Thread.interrupted())
  15.         throw new InterruptedException();
  16. }

  17. private E dequeue() {
  18.     ...
  19.     //释放
  20.     notFull.signal();
  21.     return x;
  22. }
复制代码
二、方案二:调用Thread的方法interrupted


  1. static class CreateRunable implements Runnable {
  2.     public CreateRunable(int i) {
  3.         this.i = i;
  4.     }

  5.     private int i;

  6.     public int getI() {
  7.         return i;
  8.     }

  9.     public void setI(int i) {
  10.         this.i = i;
  11.     }

  12.     @Override
  13.     public  void run() {
  14.         synchronized (this){
  15.             while ( !Thread.currentThread().isInterrupted() ){
  16.                 System.out.println("Runable接口,实现线程"+i++);
  17.             }
  18.         }
  19.     }
  20. }
复制代码
  1. Thread createThread = new Thread(new CreateRunable(0));
  2. createThread.start();
  3. Thread.sleep(5);
  4. createThread.interrupt();
复制代码
休眠5毫秒后,该线程检查到了中断信号,就会停止线程。
那如果任务正在休眠状态,线程会如何处理呢



  1. @Override
  2. public  void run() {
  3.     synchronized (this){
  4.         while ( !Thread.currentThread().isInterrupted() ){
  5.             try {
  6.                 Thread.sleep(10000);
  7.             } catch (InterruptedException e) {
  8.                 e.printStackTrace();
  9.             }
  10.             System.out.println("Runable接口,实现线程"+i++);
  11.         }
  12.     }
  13. }
复制代码



抛出异常,同时清除中断状态,线程会继续执行
  1. Runable接口,实现线程0
  2. java.lang.InterruptedException: sleep interrupted
  3.   at java.lang.Thread.sleep(Native Method)
  4.   at main.Thread.threadStartThreeWays$CreateRunable.run(threadStartThreeWays.java:48)
  5.   at java.lang.Thread.run(Thread.java:748)
  6. Runable接口,实现线程1
  7. Runable接口,实现线程2
  8. Runable接口,实现线程3
复制代码


总结
我们在这里就不说Stop()方法,因为他太暴力了,不够优雅。这里的优雅指的是可以让线程有时间做好收尾工作,避免数据的错乱。 优雅停下线程的方式主要有两种
  • 方案一:使用volatile标记位。
  • 方案二:调用Thread的方法interrupted。
通过上面的demo案例,我们可以看到使用方案一的volatile,在某一些特殊的场景下,会发生不能关闭线程的情况。
所以volatile是不够全面的。方案二则是一种更优的选择。














本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

使用道具 举报

特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们(3513994353@qq.com)。
您需要登录后才可以回帖 登录 | 立即注册
楼主
西西子

关注0

粉丝0

帖子19

最新动态