对于线程的停止,最优雅的方式就是通过 interrupt 的方式来实现,关于他的详细文章看之前文章即可,如 InterruptedException 时,再次中断设置,让程序能后续继续进行终止操作。不过对于 interrupt 实现线程的终止在实际开发中发现使用的并不是很多,很多都可能喜欢另一种方式,通过标记位。
用 volatile 标记位的停止方法关于 volatile 作为标记位的核心就是他的可见性特性,我们通过一个简单代码来看:
/** * @ulr: i-code.online * @author: zhoucx * @time: 2020/9/25 14:45 */ public class MarkThreadTest { //定义标记为 使用 volatile 修饰 private static volatile boolean mark = false; @Test public void markTest(){ new Thread(() -> { //判断标记位来确定是否继续进行 while (!mark){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程执行内容中..."); } }).start(); System.out.println("这是主线程走起..."); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //10秒后将标记为设置 true 对线程可见。用volatile 修饰 mark = true; System.out.println("标记位修改为:"+mark); } }上面代码也是我们之前文中的,这里不再阐述,就是一个设置标记,让线程可见进而终止程序,这里我们需要讨论的是,使用 volatile 是真的都是没问题的,上述场景是没问题,但是在一些特殊场景使用 volatile 时是存在问题的,这也是需要注意的!
这里我们使用一个生产/消费的模式来实现一个 Demo
/** * @url: i-code.online * @author: zhoucx * @time: 2020/10/12 10:46 */ public class Producter implements Runnable { //标记是否需要产生数字 public static volatile boolean mark = true; BlockingQueue<Integer> numQueue; public Producter(BlockingQueue numQueue){ this.numQueue = numQueue; } @Override public void run() { int num = 0; try { while (num < 100000 && mark){ //生产数字,加入到队列中 if (num % 50 == 0 ){ System.out.println(num + " 是50的倍数,加入队列"); numQueue.put(num); } num++; } } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println("生产者运行结束...."); } } }首先,声明了一个生产者 Producer,通过 volatile 标记的初始值为 true 的布尔值 mark 来停止线程。而在 run() 方法中,while 的判断语句是 num 是否小于 100000 及 mark 是否被标记。while 循环体中判断 num 如果是 50 的倍数就放到 numQueue 仓库中,numQueue 是生产者与消费者之间进行通信的存储器,当 num 大于 100000 或被通知停止时,会跳出 while 循环并执行 finally 语句块,告诉大家“生产者运行结束”
而对于消费者 Consumer,它与生产者共用同一个仓库 numQueue,在 run() 方法中我们通过判断随机数大小来确定是否要继续消费,刚才生产者生产了一些 50 的倍数供消费者使用,消费者是否继续使用数字的判断条件是产生一个随机数并与 0.97 进行比较,大于 0.97 就不再继续使用数字。
主函数中很简单,创建一个 公共仓库 queue 长度为10,然后传递给两个线程,然后启动两个线程,当我们启动后要注意,我们的消费时有睡眠 100 毫秒,那么这个公共仓库必然会被生产者装满进入阻塞,等待消费。
当消费者不再需要数据,就会将 canceled 的标记位设置为 true,理论上此时生产者会跳出 while 循环,并打印输出“生产者运行结束”。
然而结果却不是我们想象的那样,尽管已经把 Producter.mark设置成 false,但生产者仍然没有停止,这是因为在这种情况下,生产者在执行 numQueue.put(num) 时发生阻塞,在它被叫醒之前是没有办法进入下一次循环判断 Producter.mark的值的,所以在这种情况下用 volatile 是没有办法让生产者停下来的,相反如果用 interrupt 语句来中断,即使生产者处于阻塞状态,仍然能够感受到中断信号,并做响应处理。