停止线程的正确姿势

  • 说明
  • 错误的停止方式
  • 停止线程的正确姿势
  • 无法响应中断时如何停止线程
  • 总结


说明

可能很多小伙伴对线程停止是一个模糊的概念,甚至误用了一些错误的停止方式,如果在开发中将造成不可设想的后果,学好线程是进阶的一大途径,本文将为你讲解如何错误的停止方法以及正确的停止线程

错误的停止方式

1.suspend()

官方已经说明废弃,因为该方法线程带着锁去睡眠的,可想而知,效率非常低,以及非常容易造成死锁

java thread 中止一个正在执行的selenium任务_多进程


代码演示:

public class SuspendTest{

    public static void main(String[] args) throws InterruptedException {
        SuspendTest suspendTest = new SuspendTest();
        ThreadTest threadTest = suspendTest.new ThreadTest();
        Thread thread1 = new Thread(threadTest);
        thread1.start();
        // 如果目标线程(thread1)在挂起时保护关键系统资源的监视器上的锁定,则在目标线程恢复之前,线程(thread2)不能访问该资源。
        //如果要恢复目标线程的线程在调用resume之前尝试锁定此监视器, resume导致死锁。
        thread1.suspend();
        Thread thread2 = new Thread(threadTest);
        thread2.start();
//        Thread.sleep(3000);
        //唤醒线程
//        thread1.resume();
//        thread2.resume();
    }


    class ThreadTest implements Runnable{

        @Override
        public void run() {
            System .out.println(Thread.currentThread().getName()+"执行了Run方法");
        }
    }
}

一直带着锁阻塞 直到resume唤醒,下一个线程才能访问资源

java thread 中止一个正在执行的selenium任务_队列_02


2.stop()

同样是被官方弃用,线程停止,我们要有一个概念,我们不能立即关闭中断线程,这样非常不安全,容易造成数据遗失,垃圾机制不能正常回收,就好比我们的电脑突然关机一样,这是一种不好的思想

java thread 中止一个正在执行的selenium任务_多进程_03


3.通过Boolean值停止线程

这里相信很多朋友都应该使用过这种方式去停止线程,但是这种方式在一些情况下是不可取的,有些小伙伴们就说,我明明用的可好了,那我们用代码演示来说话

1.首先我们创建一个生产者和消费者
2.生产者生产速度比较快,当队列满时,进行阻塞,等待消费者消费
3.消费者需要一定时间进行消费
4.结束消费者的同时,结束生产者

/**
 * @Description: volatile Boolean 标识是一种错误的线程停止方法
 * @Author: songbiao
 */
public class VolatileErrorTest {

    public static void main(String[] args) throws InterruptedException {
        //实例化内部类需要先实例化外部类
        VolatileErrorTest test = new VolatileErrorTest();
        //阻塞队列 大小超过10会阻塞
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(10);
        //创建生产者
        Producer producer = test.new Producer(blockingQueue);
        //创建消费者
        Customer customer = test.new Customer(blockingQueue);

        //创建线程
        Thread thread = new Thread(producer);
        Thread thread1 = new Thread(customer);

        //启动线程
        thread.start();
        thread1.start();
        //睡眠一秒
        Thread.sleep(1000);
        //向消费者发送中断通知
        thread1.interrupt();
        //使用Boolean来尝试结束生产者 (不可取)
        producer.cancel = true;

    }

    //生产者
    class Producer implements Runnable{

        //用来判断是否结束
        Boolean cancel = false;

        ArrayBlockingQueue blockingQueue;

        public Producer(ArrayBlockingQueue blockingQueue) {
            this.blockingQueue = blockingQueue;
        }

        @Override
        public void run() {
            try {
            	//满足条件就执行
                for (int i = 0; i < 1000 && !cancel; i++) {
                    //我们写一个小小的逻辑
                    if (i % 2 != 0) {
                        //进行生产
                        blockingQueue.put(i);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
            	//执行结束会打印
                System.out.println("生产者 生产结束..");
            }
        }
    }

    //消费者
    class Customer implements Runnable {

        ArrayBlockingQueue blockingQueue;

        public Customer(ArrayBlockingQueue blockingQueue) {
            this.blockingQueue = blockingQueue;
        }

        @Override
        public void run() {
            try {
                while (true) {
                	//消费者消费所需时间
                    Thread.sleep(100);
                    //消费生产
                    System.out.println(blockingQueue.poll());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
            	//执行结束会打印
                System.out.println("消费者 消费结束...");
            }
        }
    }

}

执行结果,我们发现生产者并没有结束,程序依旧还在运行

java thread 中止一个正在执行的selenium任务_多进程_04


分析: 由此可见这并不是一种很好的停止线程方式

for (int i = 0; i < 1000 && !cancel; i++) {
                    if (i % 2 != 0) {
                        // 当数量超过10 会队列阻塞 无法去进去条件判断 无线等待
                        blockingQueue.put(i);
                    }
                }

停止线程的正确姿势

1.传递中断通知

@Override
    public void run() {
    	//每次循环都会判断当前线程是否需要中断
        for (int i = 0; i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted(); i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InterruptStopTest interruptStopTest = new InterruptStopTest();
        Thread thread = new Thread(interruptStopTest);
        //启动线程
        thread.start();
        //睡眠10毫秒
        Thread.sleep(10);

        //传递中断通知
        thread.interrupt();
    }

执行结果,到1466的时候就接受到了中断通知,跳出循环

java thread 中止一个正在执行的selenium任务_多线程_05


2.响应中断

同样是使用interrup方法来中断

@Override
    public void run() {
        try {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                if (i % 2 != 0) {
                    //sleep响应中断 抛出异常
                    Thread.sleep(20);
                    System.out.println(i);
                }
            }
        } catch (InterruptedException e) {
        	//注意: sleep方法会清除中断通知
            System.out.println(Thread.currentThread().isInterrupted());
            System.out.println("线程执行结束");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InterruptStopTest02 test02 = new InterruptStopTest02();
        Thread thread = new Thread(test02);
        thread.start();

        Thread.sleep(2000);

        //传递中断通知
        thread.interrupt();
    }

执行结果,正确停止

java thread 中止一个正在执行的selenium任务_多进程_06

无法响应中断时如何停止线程

如果线程阻塞是由于调用了 wait(),sleep() 或 join() 方法,你可以中断线程,通过抛 出
InterruptedException 异常来唤醒该线程。
但是对于不能响应InterruptedException的阻塞,很遗憾,并没有一个通用的解决方 案。
但是我们可以利用特定的其它的可以响应中断的方法,比如
ReentrantLock.lockInterruptibly(),比如关闭套接字使线程立即返回等方法来达到目的。
答案有很多种,因为有很多原因会造成线程阻塞,所以针对不同情况,唤起的方法也不同。

总结就是说如果不支持响应中断,就要用特定方法来唤起,没有万能药

总结

停止线程的最优方式就是通过interrupt方法传递线程响应中断,然后在run方法中进行中断处理,需要两个线程之间的配合
如果你看完了这篇文章,请试着纠正文章中的第三个错误停止案例,那么代表你已经掌握了

本文中都是通过实现Runnable接口来创建启动线程,如果对此不是很理解的话 可以看我上一篇文章。