• 3.Java中volatile 标记位的停止方法为什么不能用?


    这个之前被问过好几次,所以单独来聊一下。

    在java中几种停止线程的错误方法:

    比如 stop(),suspend() 和 resume(),这些方法已经被 Java 直接标记为 @Deprecated。

    为什么不能用这些方法呢?

    • 是因为 stop() 会直接把线程停止,这样就没有给线程足够的时间来处理想要在停止前保存数据的逻辑,任务戛然而止,会导致出现数据完整性等问题。
    • suspend() 和 resume() 而言,它们的问题在于如果线程调用 suspend(),它并不会释放锁,就开始进入休眠,但此时有可能仍持有锁,这样就容易导致死锁问题,因为这把锁在线程被继续之前,是不会被释放的。

    举例:假设线程 A 调用了 suspend() 方法让线程 B 挂起,线程 B 进入休眠,而线程 B 又刚好持有一把锁,此时假设线程 A 想访问线程 B 持有的锁,但由于线程 B 并没有释放锁就进入休眠了,所以对于线程 A 而言,此时拿不到锁,也会陷入阻塞,那么线程 A 和线程 B 就都无法继续向下执行。

    volatile 修饰标记位不适用的场景:

    1.错误代码解析:

    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    
    /**
     * @author fei.chen
     * @description: 测试 volatile 在生产消费中无效 案例
     * @date 2022/11/21下午 2:16
     */
    public class test {
      
      // 1. 声明了一个生产者 Producer,通过 volatile 标记的初始值为 false 的布尔值 canceled 来停止线程。
      // 2. 在 run() 方法中,while 的判断语句是 num 是否小于 100000 及 canceled 是否被标记。
      // 3. while 循环体中判断 num 如果是 50 的倍数就放到 storage 仓库中,storage 是生产者与消费者之间进行通信的存储器,当 num 大于 100000 或被通知停止时,会跳出 while 循环并执行 finally 语句块,告诉大家“生产者结束运行”。
        static class Producer implements Runnable {
            public volatile boolean canceled = false;
            BlockingQueue storage;
            public Producer(BlockingQueue storage) {
                this.storage = storage;
            }
    
            @Override
            public void run() {
                int num = 0;
                try {
                    while (num <= 100000 && !canceled) {
                        if (num % 50 == 0) {
                            storage.put(num);
                            System.out.println(num + "是50的倍数,被放到仓库中了。");
                        }
                        num++;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("生产者结束运行");
                }
            }
        }
    
        //对于消费者 Consumer,它与生产者共用同一个仓库 storage,并且在方法内通过 needMoreNums() 方法判断是否需要继续使用更多的数字
        //刚才生产者生产了一些 50 的倍数供消费者使用,消费者是否继续使用数字的判断条件是产生一个随机数并与 0.97 进行比较,大于 0.97 就不再继续使用数字.
        static class Consumer {
            BlockingQueue storage;
            public Consumer(BlockingQueue storage) {
                this.storage = storage;
            }
            public boolean needMoreNums() {
                if (Math.random() > 0.97) {
                    return false;
                }
                return true;
            }
        }
    
    
        // main 函数,首先创建了生产者/消费者共用的仓库 BlockingQueue storage,仓库容量是 8.
        //并且建立生产者并将生产者放入线程后启动线程,启动后进行 500 毫秒的休眠.
        //休眠时间保障生产者有足够的时间把仓库塞满,而仓库达到容量后就不会再继续往里塞,这时生产者会阻塞,500 毫秒后消费者也被创建出来,并判断是否需要使用更多的数字,然后每次消费后休眠 100 毫秒,这样的业务逻辑是有可能出现在实际生产中的。
        public static void main(String[] args) throws InterruptedException {
            ArrayBlockingQueue storage = new ArrayBlockingQueue(8);
    
            Producer producer = new Producer(storage);
            Thread producerThread = new Thread(producer);
            producerThread.start();
            Thread.sleep(500);
    
            Consumer consumer = new Consumer(storage);
            while (consumer.needMoreNums()) {
                System.out.println(consumer.storage.take() + "被消费了");
                Thread.sleep(100);
            }
            System.out.println("消费者不需要更多数据了。");
    
            //一旦消费不需要更多数据了,我们应该让生产者也停下来,但是实际情况却停不下来
            producer.canceled = true;
            System.out.println(producer.canceled);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    当消费者不再需要数据,就会将 canceled 的标记位设置为 true,理论上此时生产者会跳出 while 循环,并打印输出“生产者运行结束”。

    然而结果却不是我们想象的那样,尽管已经把 canceled 设置成 true,但生产者仍然没有停止;

    这是因为在这种情况下,生产者在执行 storage.put(num) 时发生阻塞

    在它被叫醒之前是没有办法进入下一次循环判断 canceled 的值的,所以在这种情况下用 volatile 是没有办法让生产者停下来的;

    相反如果用 interrupt 语句来中断,即使生产者处于阻塞状态,仍然能够感受到中断信号,并做响应处理。

    返回:
    
    0是50的倍数,被放到仓库中了。
    50是50的倍数,被放到仓库中了。
    100是50的倍数,被放到仓库中了。
    150是50的倍数,被放到仓库中了。
    200是50的倍数,被放到仓库中了。
    250是50的倍数,被放到仓库中了。
    300是50的倍数,被放到仓库中了。
    350是50的倍数,被放到仓库中了。
    400是50的倍数,被放到仓库中了。
    0被消费了
    50被消费了
    450是50的倍数,被放到仓库中了。
    100被消费了
    500是50的倍数,被放到仓库中了。
    150被消费了
    550是50的倍数,被放到仓库中了。
    消费者不需要更多数据了。
    true
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.正确修改代码:

    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    /**
     * @author fei.chen
     * @description: interrupt 
     * @date 2022/11/21下午 2:16
     */
    public class test  {
    	  public  static void main(String[] args) throws InterruptedException {
    	        ArrayBlockingQueue storage = new ArrayBlockingQueue(8);
    	
    	        Producer producer = new Producer(storage);
    	        Thread producerThread = new Thread(producer);
    	        producerThread.start();
    	        Thread.sleep(500);
    	
    	        Consumer consumer = new Consumer(storage);
    	        while (consumer.needMoreNums()) {
    	            System.out.println(consumer.storage.take() + "被消费了");
    	            Thread.sleep(100);
    	        }
    	        System.out.println("消费者不需要更多数据了。");
    	
    	        //一旦消费不需要更多数据了,我们应该让生产者也停下来,但是实际情况却停不下来
    	        producerThread.interrupt();
    	    }  
    	  static   class Consumer {
    		    BlockingQueue storage;
    	        public Consumer(BlockingQueue storage) {
    	            this.storage = storage;
    	        }
    	        public boolean needMoreNums() {
    	        	double aa = Math.random();
    	        	System.out.println("Math.random() :"+ aa +"> 0.97");
    	            if (aa > 0.97) {
    	            
    	                return false;
    	            }
    	            return true;
    	        }
    	    }
    
    	  static  class Producer implements Runnable {
    //	        public volatile boolean canceled = false;
    	        BlockingQueue storage;
    	        public Producer(BlockingQueue storage) {
    	            this.storage = storage;
    	        }
    	     
    	        @Override
    	        public void run() {
    	            int num = 0;
    	            try {
    	                while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
    	                    if (num % 50 == 0) {
    	                        storage.put(num);
    	                        System.out.println(num + "是50的倍数,被放到仓库中了。");
    	                    }
    	                    num++;
    	                }
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            } finally {
    	                System.out.println("生产者结束运行");
    	            }
    	        }
    	    }
    	  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    常问面试题:

    你知不知道如何正确停止线程?

    首先,从原理上讲应该用 interrupt 来请求中断,而不是强制停止,因为这样可以避免数据错乱,也可以让线程有时间结束收尾工作。

    如果我们是子方法的编写者,遇到了 interruptedException,应该如何处理呢?

    我们可以把异常声明在方法中,以便顶层方法可以感知捕获到异常,或者也可以在 catch 中再次声明中断,这样下次循环也可以感知中断,所以要想正确停止线程就要求我们停止方,被停止方,子方法的编写者相互配合,大家都按照一定的规范来编写代码,就可以正确地停止线程了。

    哪些方法停止线程是不够好的?

    比如说已经被舍弃的 stop()、suspend() 和 resume(),它们由于有很大的安全风险比如死锁风险而被舍弃,而 volatile 这种方法在某些特殊的情况下,比如线程被长时间阻塞的情况,就无法及时感受中断,所以 volatile 是不够全面的停止线程的方法。

  • 相关阅读:
    宝塔面板部署express以及MySql项目
    var 、let 和 const 的区别
    Word另存为PDF后无导航栏解决办法
    Unity Shader - sahder变体剔除
    【小程序源码】笑话段子手
    docker安装和使用
    封装适用于CentOS7的MySQL离线包
    利用 lxml 库的XPath()方法在网页中快速查找元素
    GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图
    vscode 右侧滚动条标记不提示,问题解决纪录
  • 原文地址:https://blog.csdn.net/daohangtaiqian/article/details/127963714