3.Java中volatile 标记位的停止方法为什么不能用?
创始人
2024-02-06 16:45:29
0

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

在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;}@Overridepublic 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);}}

当消费者不再需要数据,就会将 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

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;}@Overridepublic 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("生产者结束运行");}}}}

常问面试题:

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

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

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

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

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

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

相关内容

热门资讯

Python|位运算|数组|动... 目录 1、只出现一次的数字(位运算,数组) 示例 选项代...
张岱的人物生平 张岱的人物生平张岱(414年-484年),字景山,吴郡吴县(今江苏苏州)人。南朝齐大臣。祖父张敞,东...
西游西后传演员女人物 西游西后传演员女人物西游西后传演员女人物 孙悟空 六小龄童 唐僧 徐少华 ...
名人故事中贾岛作诗内容简介 名人故事中贾岛作诗内容简介有一次,贾岛骑驴闯了官道.他正琢磨着一句诗,名叫《题李凝幽居》全诗如下:闲...
和男朋友一起优秀的文案? 和男朋友一起优秀的文案?1.希望是惟一所有的人都共同享有的好处;一无所有的人,仍拥有希望。2.生活,...
戴玉手镯的好处 戴玉手镯好还是... 戴玉手镯的好处 戴玉手镯好还是碧玺好 女人戴玉?戴玉好还是碧玺好点佩戴手镯,以和田玉手镯为佳!相嫌滑...
依然什么意思? 依然什么意思?依然(汉语词语)依然,汉语词汇。拼音:yī    rán基本解释:副词,指照往常、依旧...
高尔基的散文诗 高尔基的散文诗《海燕》、《大学》、《母亲》、《童年》这些都是比较出名的一些代表作。
心在飞扬作者简介 心在飞扬作者简介心在飞扬作者简介如下。根据相关公开资料查询,心在飞扬是一位优秀的小说作者,他的小说作...
卡什坦卡的故事赏析? 卡什坦卡的故事赏析?讲了一只小狗的故事, 我也是近来才读到这篇小说. 作家对动物的拟人描写真是惟妙...
林绍涛为简艾拿绿豆糕是哪一集 林绍涛为简艾拿绿豆糕是哪一集第三十二集。 贾宽认为是阎帅间接导致刘映霞住了院,第二天上班,他按捺不...
小爱同学是女生吗小安同学什么意... 小爱同学是女生吗小安同学什么意思 小爱同学,小安同学说你是女生。小安是男的。
内分泌失调导致脸上长斑,怎么调... 内分泌失调导致脸上长斑,怎么调理内分泌失调导致脸上长斑,怎么调理先调理内分泌,去看中医吧,另外用好的...
《魔幻仙境》刺客,骑士人物属性... 《魔幻仙境》刺客,骑士人物属性加点魔幻仙境骑士2功1体质
很喜欢她,该怎么办? 很喜欢她,该怎么办?太冷静了!! 太理智了!爱情是需要冲劲的~不要考虑着考虑那~否则缘...
言情小说作家 言情小说作家我比较喜欢匪我思存的,很虐,很悲,还有梅子黄时雨,笙离,叶萱,还有安宁的《温暖的玄》 小...
两个以名人的名字命名的风景名胜... 两个以名人的名字命名的风景名胜?快太白楼,李白。尚志公园,赵尚志。
幼儿教育的代表人物及其著作 幼儿教育的代表人物及其著作卡尔威特的《卡尔威特的教育》,小卡尔威特,他儿子成了天才后写的《小卡尔威特...
海贼王中为什么说路飞打凯多靠霸... 海贼王中为什么说路飞打凯多靠霸气升级?凯多是靠霸气升级吗?因为之前刚到时确实打不过人家因为路飞的实力...
运气不好拜财神有用吗运气不好拜... 运气不好拜财神有用吗运气不好拜财神有没有用1、运气不好拜财神有用。2、拜财神上香前先点蜡烛,照亮人神...