多线程进阶--线程的中断

java为我们提供了一种调用interrupt()方法来请求终止线程的方法,下面我们就一起来学习一下线程的中断。

每一个线程都有一个boolean类型标志,用来表明当前线程是否请求中断,当一个线程调用interrupt() 方法时,线程的中断标志将被设置为true。
我们可以通过调用Thread.currentThread().isInterrupted()或者Thread.interrupted()来检测线程的中断标志是否被置位。这两个方法的区别是
Thread.currentThread().isInterrupted()是线程对象的方法,调用它后不清除线程中断标志位;而Thread.interrupted()是一个静态方法,调用它会清除
线程中断标志位。

Thread.currentThread().isInterrupted(): 对象方法 不清除中断标志位
Thread.interrupted(): 静态方法 清除中断标志位(设置为false)

所以说调用线程的interrupt() 方法不会中断一个正在运行的线程,这个机制只是设置了一个线程中断标志位,如果在程序中你不检测线程中断标志位,那么即使
设置了中断标志位为true,线程也一样照常运行。

一般来说中断线程分为三种情况:
(一) :中断非阻塞线程
(二):中断阻塞线程
(三):不可中断线程

中断非阻塞线程

中断非阻塞线程通常有两种方式:
(1)采用线程共享变量
这种方式比较简单可行,需要注意的一点是共享变量必须设置为volatile,这样才能保证修改后其他线程立即可见。

public class InterruptThreadTest extends Thread{  

    // 设置线程共享变量  
    volatile boolean isStop = false;  

    public void run() {  
        while(!isStop) {  
            long beginTime = System.currentTimeMillis();  
            System.out.println(Thread.currentThread().getName() + "is running");  
            // 当前线程每隔一秒钟检测一次线程共享变量是否得到通知  
            while (System.currentTimeMillis() - beginTime < 1000) {}  
        }  
        if (isStop) {  
            System.out.println(Thread.currentThread().getName() + "is interrupted");  
        }  
    }  

    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        InterruptThreadTest itt = new InterruptThreadTest();  
        itt.start();  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        // 线程共享变量设置为true  
        itt.isStop = true;  
    }  

}  

(2) 采用中断机制
代码如下:

public class InterruptThreadTest2 extends Thread{  
    public void run() {  
        // 这里调用的是非清除中断标志位的isInterrupted方法  
        while(!Thread.currentThread().isInterrupted()) {  
            long beginTime = System.currentTimeMillis();  
            System.out.println(Thread.currentThread().getName() + "is running");  
            // 当前线程每隔一秒钟检测线程中断标志位是否被置位  
            while (System.currentTimeMillis() - beginTime < 1000) {}  
        }  
        if (Thread.currentThread().isInterrupted()) {  
            System.out.println(Thread.currentThread().getName() + "is interrupted");  
        }  
    }  

    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        InterruptThreadTest2 itt = new InterruptThreadTest2();  
        itt.start();  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        // 设置线程的中断标志位  
        itt.interrupt();  
    }  
} 

中断阻塞线程

当线程调用Thread.sleep()、Thread.join()、object.wait()再或者调用阻塞的i/o操作方法时,都会使得当前线程进入阻塞状态。那么此时如果在线程处于阻塞状态是调用
interrupt() 方法设置线程中断标志位时会出现什么情况呢! 此时处于阻塞状态的线程会抛出一个异常,并且会清除线程中断标志位(设置为false)。这样一来线程就能退出
阻塞状态。当然抛出异常的方法就是造成线程处于阻塞状态的Thread.sleep()、Thread.join()、object.wait()这些方法。
代码实例如下:

public class InterruptThreadTest3 extends Thread{  

    public void run() {  
        // 这里调用的是非清除中断标志位的isInterrupted方法  
        while(!Thread.currentThread().isInterrupted()) {  
            System.out.println(Thread.currentThread().getName() + " is running");  
            try {  
                System.out.println(Thread.currentThread().getName() + " Thread.sleep begin");  
                Thread.sleep(1000);  
                System.out.println(Thread.currentThread().getName() + " Thread.sleep end");  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                //由于调用sleep()方法清除状态标志位 所以这里需要再次重置中断标志位 否则线程会继续运行下去  
                Thread.currentThread().interrupt();  
                e.printStackTrace();  
            }  
        }  
        if (Thread.currentThread().isInterrupted()) {  
            System.out.println(Thread.currentThread().getName() + "is interrupted");  
        }  
    }  

    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        InterruptThreadTest3 itt = new InterruptThreadTest3();  
        itt.start();  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        // 设置线程的中断标志位  
        itt.interrupt();  
    }  
}  

需要注意的地方就是 Thread.sleep()、Thread.join()、object.wait()这些方法,会检测线程中断标志位,如果发现中断标志位为true则抛出异常并且将中断标志位设置为false。
所以while循环之后每次调用阻塞方法后 都要在捕获异常之后,调用Thread.currentThread().interrupt()重置状态标志位。

不可中断线程

有一种情况是线程不能被中断的,就是调用synchronized关键字和reentrantLock.lock()获取锁的过程。
但是如果调用带超时的tryLock方法reentrantLock.tryLock(longtimeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常
有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。

public class InterruptThreadTest5 {  

    public void deathLock(Object lock1, Object lock2) {  
        try {  
            synchronized (lock1) {  
                System.out.println(Thread.currentThread().getName()+ " is running");  
                // 让另外一个线程获得另一个锁  
                Thread.sleep(10);  
                // 造成死锁  
                synchronized (lock2) {  
                    System.out.println(Thread.currentThread().getName());  
                }  
            }  
        } catch (InterruptedException e) {  
            System.out.println(Thread.currentThread().getName()+ " is interrupted");  
            e.printStackTrace();  
        }  
    }  

    public static void main(String [] args) {   

        final InterruptThreadTest5 itt = new InterruptThreadTest5();  
        final Object lock1 = new Object();  
        final Object lock2 = new Object();  
        Thread t1 = new Thread(new Runnable(){  
            public void run() {  
                itt.deathLock(lock1, lock2);  
            }  
        },"A");   
        Thread t2 = new Thread(new Runnable(){  
            public void run() {  
                itt.deathLock(lock2, lock1);  
            }  
        },"B");   

        t1.start();  
        t2.start();  
        try {  
            Thread.sleep(3000);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        // 中断线程t1、t2  
        t1.interrupt();  
        t2.interrupt();  
    }  
} 

关于reentrantLock.lock()在获取锁的过程中为什么不会被中断,先看个例子

public class Service {

    private ReentrantLock lock = new ReentrantLock();

    public void methodA(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"AAA");

            while(!Thread.currentThread().isInterrupted()){

            }
            System.out.println(Thread.currentThread().getName()+"isInterrupted:"+Thread.currentThread().isInterrupted());
        } finally{
            lock.unlock();
        }

    }

    public void methodB(){
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"isInterrupted:"+Thread.currentThread().isInterrupted());
        System.out.println(Thread.currentThread().getName()+"BBB");

        lock.unlock();
    }
}
public class Run {
    public static void main(String[] args) {
        final Service s = new Service();
        Thread ta = new Thread(new Runnable() {

            public void run() {
                s.methodA();
            }
        },"线程A");
        ta.start();

        Thread tb = new Thread(new Runnable() {

            public void run() {
                s.methodB();
            }
        },"线程B");

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        tb.start();
//      ta.interrupt();
        tb.interrupt();

    }
}

输出:
线程AAAA

可以看出来interrupt并不会中断reentrantLock.lock()的阻塞。我们知道reentrantLock.lock()阻塞线程实际上是使用LockSupport.park()来使得线程B进入阻塞状态的,但是LockSupport.park是可以被interrupt中断的,那reentrantLock.lock()却为什么不被中断呢?那是因为reentrantLock.lock()在LockSupport.park被中断以后,又再次循环使其进入阻塞状态了,也就是后面又再一次执行了LockSupport.park,使得线程进入阻塞状态

分析一下源码:

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();//若当前线程在获取锁的过程中被其他线程中断并且最终获取到锁之后,则设置
            //当前中断标志位为true。
}
private static void selfInterrupt() {
        Thread.currentThread().interrupt();//设置中断标志位为true
    }
 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;  //若当前线程被中断,则该方法会返回true
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }


 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);   //当锁被其他线程占有时,当前线程获取锁的时候获取不到。便被阻塞在
        //这里了,当其他线程释放锁的时候,会unpark这个被阻塞的线程,该方法就继续这里运行,
        return Thread.interrupted();//并且返回true,并且清除中断标志位
    }

可见当线程在reentrantLock.lock()的阻塞的时候,若被其他线程interrupt,被阻塞线程并不被中断,只是设置了被阻塞线程的中断标志位为true而已,当被阻塞线程获取到锁的时候,就可以根据Thread.currentThread().isInterrupted()判断来执行自己的逻辑,虽然不知道synchronized的内部原理是怎么样的,但是当线程被interrupt的时候,其结果也是和reentrantLock.lock()一样的,被阻塞线程并不会被中断阻塞,只是中断标志位会被设置为true。

下面给出例子:

public class Service {

    private ReentrantLock lock = new ReentrantLock();

    public synchronized void methodA(){
//      lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"AAA");

            while(!Thread.currentThread().isInterrupted()){

            }
            System.out.println(Thread.currentThread().getName()+"isInterrupted:"+Thread.currentThread().isInterrupted());
        } finally{
//          lock.unlock();
        }

    }

    public synchronized void methodB(){
//      lock.lock();
        System.out.println(Thread.currentThread().getName()+"isInterrupted:"+Thread.currentThread().isInterrupted());
        System.out.println(Thread.currentThread().getName()+"BBB");

//      lock.unlock();
    }
}
public class Run {
    public static void main(String[] args) {
        final Service s = new Service();
        Thread ta = new Thread(new Runnable() {

            public void run() {
                s.methodA();
            }
        },"线程A");
        ta.start();

        Thread tb = new Thread(new Runnable() {

            public void run() {
                s.methodB();
            }
        },"线程B");

        System.out.println("main sleep 1000");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        tb.start();

        System.out.println("线程B interrupt");
        tb.interrupt();

        System.out.println("main sleep 1000");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("线程A interrupt");
        ta.interrupt();
    }
}

输出结果:
线程AAAA
main sleep 1000
线程B interrupt
main sleep 1000
线程A interrupt
线程AisInterrupted:true
线程BisInterrupted:true
线程BBBB

你可能感兴趣的:(多线程进阶)