频道栏目
首页 > 资讯 > Java实例 > 正文

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

16-06-21        来源:[db:作者]  
收藏   我要投稿

我们知道使用stop()、suspend()等方法在终止与恢复线程有弊端,会造成线程不安全,那么问题来了,应该如何正确终止与恢复线程呢?这里可以使用两种方法:

1.使用interrupt()中断方法。

2.使用volatile boolean变量进行控制。

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

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

interrupt()方法的InterruptedException异常处理问题:

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

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

package com.rhwayfun.concurrency;


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

 

使用volatile boolean变量进行控制的问题:

 

在上面代码中,在代码中调用cancel方法来取消i的自增请求,如果Runner线程在下次执行,或者正要执行下一次自增请求时判断on的时是否变为了false,如果是则终止执行。

根据运行结果,Runner的计数任务最终会被取消,然后退出。在Runner线程最终取消执行之前,会有一定的时间,如果在在这个时间内,调用此方法的任务调用了一个会阻塞的方法,比如BlockingQueue的put方法,那么可能该任务一直违法检测到on的值变为false,因而Runner线程不会终止。

一个例子

比如下面的代码就说明了这一点:

package com.rhwayfun.patchwork.concurrency.r0411;

import java.math.BigInteger;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Created by rhwayfun on 16-4-11.
 */
public class BrokenShutdownThread extends Thread {

    //是否继续运行的标志
    private static volatile boolean on = true;
    //阻塞队列
    private final BlockingQueue queue;

    public BrokenShutdownThread(BlockingQueue queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (on) {
                //生产者一次可以放40个数
                for (int i = 0; i < 40; i++){
                    queue.put(p = p.nextProbablePrime());
                    System.out.println(Thread.currentThread().getName() + ": put value " + p);
                }
            }
        } catch (InterruptedException e) {}
    }

    public void cancel() {
        on = false;
    }

    /**
     * 消费者线程
     */
    static class Consumer extends Thread{
        //阻塞队列
        private final BlockingQueue queue;

        public Consumer(BlockingQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                while (on) {
                    //消费者一次只能消费1个数
                    System.out.println(Thread.currentThread().getName() + ": get value " + queue.take());
                }
                System.out.println("work done!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new LinkedBlockingQueue<>(5);
        BrokenShutdownThread producer = new BrokenShutdownThread(queue);
        //启动计数线程
        producer.start();
        TimeUnit.SECONDS.sleep(1);
        new Consumer(queue).start();
        TimeUnit.SECONDS.sleep(1);
        producer.cancel();
    }
}

运行上面的程序,发现虽然控制台输出了work done!的信息,但是程序仍然没有停止,仔细分析就会发现生产者的速度(40个数/次)远大于消费者的速度(1个数/次),造成队列被填满,put方法被阻塞。虽然在运行一秒后调用cancel方法将volatile变量on设为了false,但是由于生产者线程的put方法被阻塞,所以无法从阻塞的put方法中恢复,自然程序就无法终止了。

 

 

改进代码如下:

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


 

相关TAG标签
上一篇:JavaSE学习57:GUI编程之事件模型(二)
下一篇:Apache Flink fault tolerance源码剖析完结篇
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站