java 中如何正确的停止线程

1.为什么要停止线程

  • 某个正在进行的线程,用户主动取消的操作
  • 服务突然被关闭
  • 程序运行超时或者出错

2.为何说要正确的停止线程

  • 从设计原则上,Java 希望程序间能够相互通知、相互协作地管理线程,因为如果不了解对方正在做的工作,贸然强制停止线程就可能会造成一些安全的问题,为了避免造成问题就需要给对方一定的时间来整理收尾工作。
  • 举个例子: 银行每天18点下班,我17.55到银行办理存钱业务,然后柜台人员介绍了定期和活期两种存钱方式, 当我选择定期存款,然后把我的100块钱拿给柜台人员,柜台人员拿到钱的时候,正好现在18点了,柜台人员定的下班提醒闹钟到了,你说柜台人员能把钱仍在一边,然后直接关闭窗口下班么?这样是肯定不行的,她要把给你存钱的业务处理完以后,才能关闭窗口下班。

3.使用interrupt()停止线程

  • 用interrupt() 方法来通知线程中断,此方法中只是通知,而不是强制。
  • 当调用interrupt()方法以后,收到中断信号的线程是不会立即中断的,只是将自己中断标记设置为true,当线程在执行过程中,应定期去检查这个中断标记,如果中断标记是true,说明程序是想停止当前线程。

4.线程在通常三种情况下停止

4.1 普通情况

下面展示一些 内联代码片

/**
 * 1. 普通情况
 */
public class ThreadDemo2 implements Runnable {

    /**
     * 计算 10000 的倍数
     */
    @Override
    public void run() {
        int count = 1;

        /**
         *  isInterrupted() 获取当前线程的中断状态 true = 中断,false = 没中断
         *  如果程序没有加上这个 isInterrupted() 中断状态判断的话,即使发出中断信号,程序也还是是正常结束
         */
        while (count < (Integer.MAX_VALUE / 2) && !Thread.currentThread().isInterrupted()) {
            if (count % 10000 == 0) {
                System.out.println("count = " + count);
            }
            count++;
        }
        System.out.println("程序执行结束,中断信号: " + Thread.currentThread().isInterrupted());
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new ThreadDemo2());
        t1.start();
        try {
            // 阻塞两秒后,发出中断信号
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
}

程序响应中断执行结果
在这里插入图片描述
程序没有中断执行结果
在这里插入图片描述

4.2 线程阻塞情况

/**
 * 2. 线程阻塞情况
 */
public class ThreadDemo1 implements Runnable {

    public static final SimpleDateFormat FORMAT_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 每隔一秒打印当前的时间
     */
    @Override
    public void run() {
        try {
            while (true) {
                System.out.println(FORMAT_TIME.format(new Date(System.currentTimeMillis())));
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
     		// sleep()方法会响应中断,并且会抛出InterruptedException 异常,但异常抛出以后会清除中断标记
            System.out.println("捕捉到中断异常,程序退出. 中断信号: "+ Thread.currentThread().isInterrupted());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new ThreadDemo1());
        t1.start();

        try {
            // 阻塞三秒后,发出中断信号
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
}

执行结果
在这里插入图片描述

4.3 传递中断情况

但是我们开发时候会遇到把try/catch 写在循环里面,如果sleep() 期间响应中断抛出一个异常后,会清除当前线程的中断信号。
在这里插入图片描述
上述的问题解决办法可以传递中断信号来解决

public class ThreadDemo3 implements Runnable {
    public static final SimpleDateFormat FORMAT_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void run() {
        // 模仿 try/catch 在循环里面中断情况,
        while (true && !Thread.currentThread().isInterrupted()) {
            System.out.println(FORMAT_TIME.format(new Date(System.currentTimeMillis())));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                // 捕捉到中断异常以后,再次发出中断信号
                Thread.currentThread().interrupt();
                System.out.println("中断信号: " + Thread.currentThread().isInterrupted());
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new ThreadDemo3());
        t1.start();
        try {
            // 阻塞两秒后,发出中断信号
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
}

传递中断执行结果
在这里插入图片描述
还有一种方法就是在run方法中调用别的方法

public class ThreadDemo4 implements Runnable {

    public static final SimpleDateFormat FORMAT_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 每隔一秒打印当前的时间
     */
    @Override
    public void run() {
        while (true && !Thread.currentThread().isInterrupted()) {
            try {
                paintTime();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                e.printStackTrace();
            }
        }
    }

    /**
     * 该方法被线程调用,要响应中断异常,把异常向上抛出或者再次中断,而不是自己生吞
     *
     * @throws InterruptedException
     */
    public static void paintTime() throws InterruptedException {
        System.out.println(FORMAT_TIME.format(new Date(System.currentTimeMillis())));
        Thread.sleep(1000);
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new ThreadDemo4());
        t1.start();
        try {
            // 阻塞两秒后,发出中断信号
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();

    }
}

5.说说被弃用的stop(),suspend() 和 resume()方法

  • stop()方法会直接让线程停止工作。还是那上面的那个例子来说,18点了柜台小姐姐收到下班提醒闹钟了,直接把钱扔到一边,我的存钱业务也不管了,就把窗口关闭了。我存的钱给人家了,但是账户余额没有加,这样搞得话我想这家银行没开几天就会GG吧。在程序中这样做很容易会脏数据或更加严重的后果。
  • suspend() 和 resume() ,它们的问题在于如果线程调用 suspend(),它并不会释放锁,就开始进入休眠,但此时有可能仍持有锁,这样就容易导致死锁问题,因为这把锁在线程被 resume() 之前,是不会被释放的。

6.volatile 方式停止线程

public class ThreadDemo6 implements Runnable {

     static volatile boolean flag = true;
    /**
     * 计算 10000 的倍数
     */
    @Override
    public void run() {
        int count = 1;
        while (count < (Integer.MAX_VALUE / 2) && flag) {
            if (count % 10000 == 0) {
                System.out.println("count = " + count);
            }
            count++;
        }
        System.out.println("程序执行结束,flag: " + flag);
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new ThreadDemo6());
        t1.start();
        try {
            // 阻塞两秒后,发出中断信号
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        flag = false;
    }
}

这个方式和interrupt()方式基本上差不多,这是volatile的使用得看情况,像上述的这种比较简单的方式使用是没有问题,但是在线程长时间等待的时候,volatile标记是无法响应中断。

7. 响应线程中断的方法

  • Object.wait() 相关方法
  • Thread.sleep() 相关方法
  • Thread.join() 相关方法
  • java.util.concurrent包中 BlockingQueue.take() / put(E)
  • java.util.concurrent包中 Lock.lockInterruptibly()
  • java.util.concurrent包中 CountDownLatch.await()
  • java.util.concurrent包中 CyclicBarrier.await()
  • java.util.concurrent包中 Exchanger.exchange(V)
  • java.nio.channels包中 InterruptibleChannel相关方法
  • java.nio.channels包中 Selector的相关方法
  • 以上方法都是可以响应中断的 ,大约这么多。

8.判断是否中断的相关方法

  • public static boolean interrupted() // 获取线程中断标识,并且重置 ps:该方法返回的当前线程的标识,就是调用它的那个线程标识
  • public boolean isInterrupted() // 获取线程中断标识
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值