Java线程停止的坑

Java线程停止的坑_第1张图片

复杂点的Java应用一般都会开多个线程在后台跑程序,有时候因为发布或其他原因需要停止程序,那就涉及到如何停止Java线程的问题.Java 的线程停止还是比较别扭的,如果是从其他语言转学java,很容易掉进坑里面.

一 坑一 利用线程的stop方法

如果初学Java,在看到Thread类有个stop方法,那第一反应停止应该用stop吧。在好些的IDE里面会给你提示,此方法已经被标记为:@Deprecated,为什么那。那是因为stop是直接停止线程,为什么直接停止程序不行那,因程序可能还没有处理完内存中积压的数据,直接强制停止,就可能会造成数据的完整性被破坏,和我们尽量不要用kill -9 pid来这种暴力停止程序的原因一样。

那就继续找:suspendresume 看到这两个方法,将线程暂停的时候,也许会用到此方法。结果这个也是坑,这两个方法同样被标记为:@Deprecated,为什么这种暂停的方法也不建议用那,这是因为java的线程在调用suspend暂停后,并没有释放所持有的锁,这样如果其他线程刚好必须用到这把锁,迟迟获取不到,就可能进入到死锁状态。

二  坑二利用volatile 标记位

这个是从其他语言转Java的人常用的一种停止线程的方法,这种方法我以前经常用,觉得简单方便,第一次看到用这种方法停止线程是错误的,真令我惊讶。当然不是所有的情况下,都会有问题,在特定的情况下,线程会无法终止。

实例代码如下:生产者代码:

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 % 30 == 0) {
                    storage.put(num);
                    System.out.println(num + "放入队列中。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生产者结束运行");
        }
    }
}

消费者代码:

import java.util.concurrent.BlockingQueue;

class Consumer {
    BlockingQueue storage;
    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }
    public boolean needMoreNums() {
        if (Math.random() > 0.89) {
            return false;
        }
        return true;
    }
}

main函数代码:


import java.util.concurrent.ArrayBlockingQueue;

public class TestStopThread
{
    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(800);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者停止。");

        // 停止标识设置为true 停止生产者
        producer.canceled = true;
        System.out.println(producer.canceled);
    }
}

代码比较简单,生产者和消费者通过ArrayBlockingQueue 队列相连,生产者生产30倍数的数字,消费者消费,消费者每消费一个数字就会取个随机数看下是否大于0.89,大于则消费者停止消费。主线程设置生产者停止标识,标识生产者可以停止了,停止后,整个程序也应该停止了。运行结果:Java线程停止的坑_第2张图片从结果可以看出,虽然生产者的停止标识为true了,但是整个程序仍然没有停止。这是因为在生产者的循环中,storage.put(num); 队列满了之后,发生了阻塞,生产者的线程就阻塞在这里面,虽然canceled标识已经设置为true了,但是程序还没有到判断循环标志着一步,所以一直是卡着的。

三线程停止的正确方式

Java线程停止的正确姿势,是采用中断方式,把生产者的核心代码改动如下:

 @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !   Thread.currentThread().isInterrupted()) {
                if (num % 30 == 0) {
                    storage.put(num);
                    System.out.println(num + "放入队列中。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("线程检测到中断信号");
        } finally {
            System.out.println("生产者结束运行");
        }
    }

主函数改成通过中断方式来通知生产者线程停止:

        producerThread.interrupt();

运行结果:Java线程停止的坑_第3张图片可以看到中断可以打断线程的阻塞,让线程继续执行下去,而且如果线程处于sleep状态,中断仍然可以打断sleep状态,这样就不用像刚才的线程标识那样,还必须等待线程休眠完毕后才可以响应。中断发生后,我们还可以继续处理剩下的数据,这样就不像stop()方法那样处理的太生硬,而导致数据不一致问题。

isInterrupted() 会检测中断标识,检测到返回true,并将标志设置成false。所以我们在编写多线程的处理任务代码的时候,不要将中断吞并而不处理,特别有些人喜欢用catch(Execption e)方式来捕获异常,而不处理。如果框架采用这种中断方式来停止线程,那就无法停止线程。

实话实说,我觉得java这种停线程的方式很容易让人误用,没办法,就这样设计的。

四 诗词欣赏

浣溪沙·山色横侵蘸晕霞

[宋代] [苏轼] 

山色横侵蘸晕霞,湘川风静吐寒花。远林屋散尚啼鸦。
梦到故园多少路,酒醒南望隔天涯。月明千里照平沙。

你可能感兴趣的:(Java线程停止的坑)