Java并发编程系列之七:正确终止与恢复线程

标签:  Java并发编程
12人阅读 评论(0) 收藏 举报
分类:

前面提到了stop()、suspend()等方法在终止与恢复线程的弊端,那么问题来了,应该如何正确终止与恢复线程呢?这里可以使用两种方法:interrupt()方法和使用boolean变量进行控制。

在使用interrupt方法之前,有必要介绍一下中断以及与interrupt相关的方法。中断可以理解为线程的一个标志位属性,表示一个运行中的线程是否被其他线程进行了中断操作。这里提到了其他线程,所以可以认为中断是线程之间进行通信的一种方式,简单来说就是由其他线程通过执行interrupt方法对该线程打个招呼,让起中断标志位为true,从而实现中断线程执行的目的。

其他线程调用了interrupt方法后,该线程通过检查自身是否被中断进行响应,具体就是该线程需要调用isInterrupted方法进行判断是否被中断或者调用Thread类的静态方法interrupted对当前线程的中断标志位进行复位(变为false)。需要注意的是,如果该线程已经处于终结状态,即使该线程被中断过,那么调用isInterrupted方法返回仍然是false,表示没有被中断。

那么是不是线程调用了interrupt方法对该线程进行中断,该线程就会被中断呢?答案是否定的。因为Java虚拟机对会抛出InterruptedException异常的方法进行了特别处理:Java虚拟机会将该线程的中断标志位清除,然后跑出InterruptedException,这个时候调用isInterrupted方法返回的也是false

下面的代码首先创建了两个线程,一个线程内部不停睡眠,另一个则不断执行,然后对这两个线程执行中断操作。

package com.rhwayfun.concurrency;

/**
 * Created by rhwayfun on 16-4-2.
 */
public class Interrupted {

    public static void main(String[] args){
        //创建一个休眠线程
        Thread sleepThread = new Thread(new SleepThread(),"SleepThread");
        //设为守护线程
        sleepThread.setDaemon(true);
        //创建一个忙线程
        Thread busyThread = new Thread(new BusyThread(),"BusyThread");
        //把该线程设为守护线程
        //守护线程只有当其他前台线程全部退出之后才会结束
        busyThread.setDaemon(true);
        //启动休眠线程
        sleepThread.start();
        //启动忙线程
        busyThread.start();
        //休眠5秒,让两个线程充分运行
        SleepUtil.second(5);
        //尝试中断线程
        //只需要调用interrupt方法
        sleepThread.interrupt();
        busyThread.interrupt();
        //查看这两个线程是否被中断了
        System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
        System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
        //防止sleepThread和busyThread立刻退出
        SleepUtil.second(2);
    }

    /**
     * 不断休眠
     */
    static class SleepThread implements Runnable{
        public void run() {
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 不断等待
     */
    static class BusyThread implements Runnable{
        public void run() {
            while (true){
                //忙等待
            }
        }
    }
}

执行结果:


可以发现内部不停睡眠的方法执行执行中断后,其中断标志位返回的是false,而一直运行的线程的中断标志位则为true。这里主要由于Sleep方法会抛出InterruptedException异常,所以Java虚拟机把SleepThread的中断标志位复位了,所以才会显示false。

那么使用interrupt方法正确终止线程已经很明显了,代码如下:


package com.rhwayfun.concurrency;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * Created by rhwayfun on 16-4-2.
 */
public class SafeShutdownThread {
    public static void main(String[] args) throws InterruptedException {
        DateFormat format = new SimpleDateFormat("HH:mm:ss");
        Runner one = new Runner();
        //创建第一个计数线程,该线程使用jdk自带的中断方法执行中断
        Thread threadOne = new Thread(one,"ThreadOne");
        //执行第一个线程
        threadOne.start();
        //threadOne休眠一秒,然后由main thread执行中断
        TimeUnit.SECONDS.sleep(1);
        threadOne.interrupt();
        System.out.println("ThreadOne is interrupted ? " + threadOne.isInterrupted());
        System.out.println("main thread interrupt ThreadOne at " + format.format(new Date()));

        //创建第二个线程,该线程使用cancel方法执行中断
        Runner two = new Runner();
        Thread threadTwo = new Thread(two,"ThreadTwo");
        threadTwo.start();
        //休眠一秒,然后调用cancel方法中断线程
        TimeUnit.SECONDS.sleep(1);
        two.cancel();
        System.out.println("ThreadTwo is interrupted ? " + threadTwo.isInterrupted());
        System.out.println("main thread interrupt ThreadTwo at " + format.format(new Date()));
    }

    /**
     * 该线程是一个计数线程
     */
    private static class Runner implements Runnable{
        //变量i
        private long i;
        //是否继续运行的标志
        //这里使用volatile关键字可以保证多线程并发访问该变量的时候
        //其他线程都可以感知到该变量值的变化。这样所有线程都会从共享
        //内存中取值
        private volatile boolean on = true;
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()){
                i++;
            }
            System.out.println("Count i = " + i);
        }

        //让线程终止的方法
        public void cancel(){
            on = false;
        }
    }
}

在计数线程中通过使用一个boolean变量成功终止了线程。这种通过标志位或者中断操作的方式能够使得线程在终止的时候有机会去清理资源,而不是武断地将线程终止,因此这种终止线程的做法更优雅和安全。

上面的程序只是正确地终止了线程,却没有给出正确恢复的方法。可能有人会想到:再写一个方法让on变量为true不就行了。事实并如此,因为在CountThread中,由于已经调用cancel方法,这时on变量已经是false了,线程按照顺序执行原则继续执行,所以即使改变on为true也是没用的,因为CountThread已经终止了。具体的解决方法将在下一篇关于等待通知机制的文章给出详细的解决措施。


查看评论

打通Linux脉络系列:进程、线程和调度

-
  • 1970年01月01日 08:00

Java并发编程-如何终止线程

我们知道使用stop()、suspend()等方法在终止与恢复线程有弊端,会造成线程不安全,那么问题来了,应该如何正确终止与恢复线程呢?这里可以使用两种方法: 1.使用interrupt()中断方法...
  • chenchaofuck1
  • chenchaofuck1
  • 2016-06-20 16:47:07
  • 2269

Java并发编程系列之二十九:正确终止与恢复线程(续)

重新认识中断之前在正确终止与恢复线程一文中介绍了使用Thread类的interrupt方法和使用标志位实现线程的终止。由于之前只是简单介绍了jdk默认中断方法的问题,对线程的中断机制没有深入介绍。为了...
  • u011116672
  • u011116672
  • 2016-04-11 19:27:56
  • 3078

Java并发编程系列之三十:多线程的代价

线程的简单回顾在操作系统中引入多线程的原因是进程切换的开销太大,进程在进行上下文切换时由于要切换页表,往往伴随者页调度,因此开销比较大,而线程在进行上下文切换时,由于仅涉及与自身相关的寄存器状态和栈的...
  • u011116672
  • u011116672
  • 2016-04-12 18:37:22
  • 3150

浅谈Java并发编程系列(六) —— 线程池的使用

线程池的作用 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的资源浪费。 提高响应速度。当任务到达时,不需要等到线程创建就能立即执行。 方便管理线程。线程是稀缺资源,如果无限制地创建,不...
  • codershamo
  • codershamo
  • 2016-07-11 21:13:43
  • 1244

Java并发编程的艺术 (Java核心技术系列)

  • 2017年12月26日 08:53
  • 7.45MB
  • 下载

如何正确停止java中的线程

为什么不能使用Thread.stop()方法? 从SUN的官方文档可以得知,调用Thread.stop()方法是不安全的,这是因为当调用Thread.stop()方法时,会发生下面两件事: 1. ...
  • liranke
  • liranke
  • 2012-12-07 18:20:31
  • 18448

Java并发编程系列之十五:Executor框架

Java使用线程完成异步任务是很普遍的事,而线程的创建与销毁需要一定的开销,如果每个任务都需要创建一个线程将会消耗大量的计算资源,JDK 5之后把工作单元和执行机制区分开了,工作单元包括Runnabl...
  • u011116672
  • u011116672
  • 2016-04-04 17:55:04
  • 5800

【Java并发编程】之三:线程挂起、恢复与终止的正确方法(含代码)

转载请注明出处:     Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的。如果在不合适的时候挂起线程(比如,锁定共享...
  • mmc_maodun
  • mmc_maodun
  • 2013-12-04 08:12:30
  • 20150

浅谈Java并发编程系列(七) ——— 深入解析synchronized关键字

Synchronized关键字synchronized的锁机制的主要优势是Java语言内置的锁机制,因此,JVM可以自由的优化而不影响已存在的代码。任何对象都拥有对象头这一数据结构来支持锁,但是对于较...
  • codershamo
  • codershamo
  • 2016-07-30 11:54:29
  • 956
    个人资料
    持之以恒
    等级:
    访问量: 7293
    积分: 792
    排名: 6万+
    轻松一下
    文章存档
    最新评论