JAVA多线程基础篇 3、如何优雅结束一个线程


线程的结束有几种类型

  • 寿终正寝,完成了使命,自然死亡。
  • 线程在执行的过程中,因为耗时过长或者各种原因被中断。

中断线程该如何处理呢?

1. 错误的方法

1.1 stop()方法不要使用

public class StopIsNotSafe {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(()->{
            while (true) {
                System.out.println("我在换衣服");
                System.out.println("我在脱裤子");
                System.out.println("我在穿裤子");
            }
        });

        t1.start();
        Thread.sleep(10);
        t1.stop();
    }
}

运行结果,脱了裤子没有穿,程序运行到一半:

stop方法

哪怕该线程正在执行某个事务,锁定了某个对象,此时仍然会被粗暴中止,往往此时程序正在做着重要的事情啊,一旦中止本应该完整的逻辑就被破坏了。

        Object o = new Object();
        Thread t2 = new Thread(()->{
            synchronized (o) {
                while (true) {
                    System.out.println("t2 我在换衣服");
                    System.out.println("t2 我在脱裤子");
                    System.out.println("t2 我在穿裤子");
                }
            }
        });

        t2.start();
        Thread.sleep(10);
        t2.stop();

运行结果,脱了裤子没有穿,程序运行到一半:

stop方法2

1.2 suspend() 也不建议使用

很简单,因为suspend方法调用后,线程会被暂停,但是锁资源仍然没有释放。
万一忘记resume(),或者调用resume时出现了问题,就会出现死锁。
况且这个方法并不是中断,只是暂停。

public class ResumeIsNotSafe {

    public static void main(String[] args) throws InterruptedException {

        Object o = new Object();

        Thread t1 = new Thread(()->{
            for(int i=0;i<5;i++) {
                synchronized (o) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("t1 :"+ LocalDateTime.now());
                }
            }
        });
        t1.start();

        Thread.sleep(100);
        t1.suspend();
        Thread.sleep(10000);
        //如果resume失效了。则无法执行t2了。
        System.out.println("此时,t1进入暂停状态,但是它仍然持有锁");
        //t1.resume();

        Thread t2 = new Thread(()->{
            for(int i=0;i<5;i++) {
                synchronized (o) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("t2 :"+ LocalDateTime.now());
                }
            }
        });
        t2.start();
    }
}

执行结果,t2线程无法获得锁,死锁:

suspend忘记resume

2.正确的方法

2.1 使用volatile + 标志位

有句话叫做“凯撒的归凯撒,上帝的归上帝”,线程自己的事情,应该由线程自己控制。
我们可以在线程类内部设定一个flag字段,主线程通过该字段能够通知t1线程,线程监控该字段,当该字段为True,则线程正常运行,否则就停下来。

public class UsingFlagToStop {

    private static volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        //线程t1每秒打印一次
        Thread t1 = new Thread(()->{
            while (flag) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1:"+ LocalDateTime.now());
            }
        });

        t1.start();
        Thread.sleep(4000);
        flag=false;
    }
}

执行结果如下,当主线程4秒后设置flag为false后,t1线程检测到标志被设置,于是停止了运行。

suspend忘记resume

不足之处 ,volatile标志位无法及时通知正在wait()和sleep()的线程。

2.2 使用线程的interrupt()方法

了解关于interrupt有关的三个方法。

  • void interrupt() ,例如t1.interrupt(),通过设置t1线程的中断标志通知线程。此时线程会接收到InterruptedException异常,从而进行控制。
  • boolean isInterrupted() 线程可以通过该方法查询自身的中断标志。
  • static boolean interrupted() ,该方法是静态方法,查看线程当前的中断的标志,如果是被打断状态,会重置恢复标志。

2.2.1 方式1、interrupt() 后进入Exception处理。

interrupt的java内置的解决方案,能够在wait()和sleep()时,及时通知,更加优雅有效。
线程t1 一开始运行后,就睡眠,通过调用t1.interrupt(),将t1打断,不用等那么久了。

public class InterruptToStop {

    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        Thread t1 = new Thread(()->{
            synchronized(o) {
                System.out.println(LocalDateTime.now()+" t1:等等我先喝口水上个厕所");
                try {
                    Thread.sleep(10000);
                    //对于正在wait的线程也可以中断哦。
                    //o.wait();
                } catch (InterruptedException e) {
                    System.out.println(LocalDateTime.now()+" t1:摸鱼的我,被打断了,赶紧善后。");
                }
                System.out.println(LocalDateTime.now()+" t1:事情忙完了。");
            }
        });

        Thread t2 = new Thread(()->{
            synchronized(o) {
                System.out.println(LocalDateTime.now()+" t2:终于轮到我上场了");
            }
        });

        t1.start();
        //确保t1先启动,拿到锁对象o。
        Thread.sleep(50);
        t2.start();
        t1.interrupt();
    }
}

运行结果显示,t1被打断后,进入异常处理环节,在这里实现更加优雅的打断。

interrupt

2.2.2 方式2、线程不断检查自身中断状态。

也可以通过线程检查自身interrupted状态来控制。
这种方式适用于线程内部没有调用wait、sleep等方法的情况下。因为在这种情况下,是没有对InterruptedException进行特殊处理。所以需要线程不断检查interrupted状态。

public class InterruptToStop2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            while (!Thread.interrupted()) {
                System.out.println("t1 处理事情 "+ LocalDateTime.now());
            }
            System.out.println("我被打断了,做后面的事情");
        });
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}

运行结果,t1检查到被interrupt,被中断。

interrupt2

2.3 如何打断正在抢锁(BLOCKED)状态的线程

interrupt()方法对于正在抢锁的线程是无能为力的。

做一个实验,线程进入blocked状态后,通过interrupt打断,没有效果。

public class InterruptToStop3 {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        Thread t1 = new Thread(()->{
            synchronized (o) {
                System.out.println(LocalDateTime.now()+",t1:我获得了锁,我将占有10秒钟");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        //保证t1先执行
        Thread.sleep(50);

        Thread t2 = new Thread(()->{
            System.out.println(LocalDateTime.now()+",t2 我将争抢锁");
           synchronized (o) {
               System.out.println(LocalDateTime.now()+",t2:我终于抢到锁了");
           }
        });
        t2.start();

        Thread.sleep(50);
        System.out.println("t2 状态:"+ t2.getState());
        //此时无法打断。进入blocked状态。
        t2.interrupt();
    }
}

运行结果,t2非要拿到锁后继续执行。感觉是一个很执拗的线程。

interrupt和抢锁1

解决方案是,使用ReentrantLock锁对象,调用lockInterruptibly()方法加锁。

public class InterruptToStop4 {
    public static void main(String[] args)  {
        ReentrantLock o = new ReentrantLock();
        Thread t1 = new Thread(()->{
            try {
                o.lock();
                System.out.println(LocalDateTime.now()+",t1:我获得了锁,我将占有10秒钟");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {

                }
            } finally {
                o.unlock();
            }
        });
        t1.start();

        //保证t1先执行
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            //e.printStackTrace();
        }

        Thread t2 = new Thread(()->{
            System.out.println(LocalDateTime.now()+",t2 我将争抢锁");
            try {
                o.lockInterruptibly();
                System.out.println(LocalDateTime.now()+",t2 我终于拿到锁啦!!!");

            } catch (InterruptedException e) {
                System.out.println(LocalDateTime.now()+",t2 我被中断了!");
            }finally {
                if(o.isHeldByCurrentThread())
                o.unlock();
            }
        });
        t2.start();
        //保证t1先执行
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            //e.printStackTrace();
        }
        System.out.println("t2 状态:"+ t2.getState());
        //此时无法打断。进入blocked状态。
        t2.interrupt();
    }
}

运行结果显示,此时可以中断正在抢锁的线程。而且该线程其实并没有进入BLOCKED状态,而是WATING状态。也就是说JAVA对这种抢锁的方式,其处理方法是有不同的。
运行结果:
interrupt和抢锁2

总结

  • 对于线程中断的处理要交给线程自身。
  • 可以自己定义标志位来处理。
  • 也可以使用interrupt方法来处理,它能适应的场景很多,在线程sleep()、wait(),甚至抢锁时都能中断。

多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。

https://github.com/forestnlp/concurrentlab

如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。

您的支持是对我最大的鼓励。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟空学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值