理解Thread.interrupt()的含义

理解线程中断的含义

定义

         一看到线程的interrupt()方法,根据字面意思,很容易将该方法理解为中断线程。其实Thread.interrupt()并不会中断线程的运行,它的作用仅仅是为线程设定一个状态而已,即标明线程是中断状态,这样线程的调度机制或我们的代码逻辑就可以通过判断这个状态做一些处理,比如sleep()方法会抛出异常,或是我们根据isInterrupted()方法判断线程是否处于中断状态,然后做相关的逻辑处理。

        下面我们看一下官方对interrupt()的解释:

      void java.lang.Thread.interrupt()

       Interrupts this thread.

       Unless the current thread is interrupting itself, which is always permitted, thecheckAccess method of this thread is invoked, which may cause aSecurityException to be thrown.

       If this thread is blocked in an invocation of thewait(),wait(long), orwait(long, int) methods of theObject class, or of thejoin(),join(long),join(long, int),sleep(long), orsleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive anInterruptedException.

If this thread is blocked in an I/O operation upon aninterruptible channel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive ajava.nio.channels.ClosedByInterruptException.

      If this thread is blocked in a java.nio.channels.Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeup method were invoked.

If none of the previous conditions hold then this thread's interrupt status will be set.

Interrupting a thread that is not alive need not have any effect.

      Throws:

SecurityException - if the current thread cannot modify this thread

       通过解释可以看出:处于阻塞的线程,即在执行Object对象的wait()、wait(long)、wait(long, int),或者线程类的join()、join(long)、join(long, int)、sleep(long)、sleep(long,int)方法后线程的状态,当线程调用interrupt()方法后,这些方法将抛出InterruptedException异常,并清空线程的中断状态,即isInterrupted()返回false

示例

      例1:

public class Test {
	public static void main(String[] args) {
		Thread td = new Thread(new Runnable(){
			@Override
			public void run() {
				try {
					Thread.sleep(100000L);
				} catch (InterruptedException e) {
					System.out.println("线程是否处于中断状态" + Thread.currentThread().isInterrupted());
					e.printStackTrace();
					System.out.println("abc");
				}
				System.out.println("def");
			}
		});
		td.start();
		td.interrupt();
	}
}

代码执行结果:

线程是否处于中断状态false
abc
def
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at Test3$1.run(Test3.java:7)
	at java.lang.Thread.run(Thread.java:722)

     例2:

public class Test2 {
	public static void main(String[] args) {
		Thread td = new Thread(new Runnable(){
			@Override
			public void run() {
				System.out.println("线程是否处于中断状态" + Thread.currentThread().isInterrupted());
			}
		});
		td.start();
		td.interrupt();
	}
}

运行结果

线程是否处于中断状态true

通过这个例子可以看出:

  •     虽然线程发起的中断动作td.interrupt()但是线程并没有中断,仍然打印了def
  •     当线程发送中断请求后,线程的中断状态被置为true
  •     当线程发送中断请求后,sleep()方法抛出了InterruptedException异常,并且将线程的中断状态重置为false


关于中断方法介绍

看看Thread类里的几个方法:

  


public static boolean 
interrupted                                                                                  
测试当前线程是否已经中断。如果线程处于中断状态返回true,否则返回false。同时该方法将清除的线程的中断状态。即,如果连续两次调用该方法,则第二次调用将返回 false。该方法可用于清除线程中断状态使用。

public boolean 
isInterrupted()
测试线程是否已经中断。线程的中断状态不受该方法的影响。

public void 
interrupt()
中断线程。

 
  

中断状态的使用

线程同步机制中

       如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则线程会一直检查中断状态标示,如果发现中断状态标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。

      注,synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。


程序逻辑判断的条件

          比如我们在代码逻辑中有这样的设计,当线程处于中断状态时,不做一些处理工作

public void run() {
     ...
     while (!Thread.currentThread().isInterrupted()&& more work to do) {
         do more work 
     }
}

线程中断的几种情况

使用中断信号量中断非阻塞状态的线程

中断线程最好的,最受推荐的方式是,使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量,然后有秩序地中止任务。Example2描述了这一方式:

class Example2 extends Thread {
    volatile boolean stop = false;// 线程中断信号量

    public static void main(String args[]) throws Exception {
        Example2 thread = new Example2();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        // 设置中断信号量
        thread.stop = true;
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    public void run() {
        // 每隔一秒检测一下中断信号量
        while (!stop) {
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            /*
             * 使用while循环模拟 sleep 方法,这里不要使用sleep,否则在阻塞时会 抛
             * InterruptedException异常而退出循环,这样while检测stop条件就不会执行,
             * 失去了意义。
             */
            while ((System.currentTimeMillis() - time < 1000)) {}
        }
        System.out.println("Thread exiting under request...");
    }
}

使用thread.interrupt()中断非阻塞状态线程

虽然Example2该方法要求一些编码,但并不难实现。同时,它给予线程机会进行必要的清理工作。这里需注意一点的是需将共享变量定义成volatile 类型或将对它的一切访问封入同步的块/方法(synchronized blocks/methods)中。上面是中断一个非阻塞状态的线程的常见做法,但对非检测isInterrupted()条件会更简洁:

class Example2 extends Thread {
    public static void main(String args[]) throws Exception {
        Example2 thread = new Example2();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        // 发出中断请求
        thread.interrupt();
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    public void run() {
        // 每隔一秒检测是否设置了中断标示
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            // 使用while循环模拟 sleep
            while ((System.currentTimeMillis() - time < 1000) ) {
            }
        }
        System.out.println("Thread exiting under request...");
    }
}

到目前为止一切顺利!但是,当线程等待某些事件发生而被阻塞,又会发生什么?当然,如果线程被阻塞,它便不能核查共享变量,也就不能停止。这在许多情况下会发生,例如调用Object.wait()、ServerSocket.accept()和DatagramSocket.receive()时,这里仅举出一些。
他们都可能永久的阻塞线程。即使发生超时,在超时期满之前持续等待也是不可行和不适当的,所以,要使用某种机制使得线程更早地退出被阻塞的状态。下面就来看一下中断阻塞线程技术。


使用thread.interrupt()中断阻塞状态线程

Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,设置线程的中断标示位,在线程受到阻塞的地方(如调用sleep、wait、join等地方)抛出一个异常InterruptedException,并且中断状态也将被清除,这样线程就得以退出阻塞的状态。下面是具体实现:

class Example3 extends Thread {
    public static void main(String args[]) throws Exception {
        Example3 thread = new Example3();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        thread.interrupt();// 等中断信号量设置后再调用
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread running...");
            try {
                /*
                 * 如果线程阻塞,将不会去检查中断信号量stop变量,所 以thread.interrupt()
                 * 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并
                 * 进行异常块进行 相应的处理
                 */
                Thread.sleep(1000);// 线程阻塞,如果线程收到中断操作信号将抛出异常
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted...");
                /*
                 * 如果线程在调用 Object.wait()方法,或者该类的 join() 、sleep()方法
                 * 过程中受阻,则其中断状态将被清除
                 */
                System.out.println(this.isInterrupted());// false

                //中不中断由自己决定,如果需要真真中断线程,则需要重新设置中断位,如果
                //不需要,则不用调用
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Thread exiting under request...");
    }
}
一旦Example3中的Thread.interrupt()被调用,线程便收到一个异常,于是逃离了阻塞状态并确定应该停止。上面我们还可以使用共享信号量来替换!Thread.currentThread().isInterrupted()条件,但不如它简洁。

死锁状态线程如何被中断

        所谓死锁就是两个线程都在努力获得对方占用锁,导致程序处于死锁状态,无法执行下去。关于死锁代码中获取锁的方式有5中,包括synchronized关键字或ReentrantLock类的lock()、lockInterruptibly()、tryLock()或 tryLock(long, java.util.concurrent.TimeUnit)4个实现方法,其中lockInterruptibly()和 tryLock(long, java.util.concurrent.TimeUnit)会在获取锁的过程中检测中断状态,一旦发现线程处于中断状态立即抛出InterruptedException异常;其他三种方式则不检测中断异常。下面分别介绍下不可中断死锁和可中断死锁的例子。

不可中断死锁

Example4试着去中断处于死锁状态的两个线程,但这两个线都没有收到任何中断信号(抛出异常),所以interrupt()方法是不能中断死锁线程的,因为锁定的位置根本无法抛出异常:

public class Example4 extends Thread {
    public static void main(String args[]) throws Exception {
        final Object lock1 = new Object();
        final Object lock2 = new Object();
        Thread thread1 = new Thread() {
            public void run() {
                deathLock(lock1, lock2);
            }
        };
        Thread thread2 = new Thread() {
            public void run() {
                // 注意,这里在交换了一下位置
                deathLock(lock2, lock1);
            }
        };
        System.out.println("Starting thread...");
        thread1.start();
        thread2.start();
        Thread.sleep(20);
        System.out.println("Interrupting thread...");
        thread1.interrupt();
        thread2.interrupt();
        //Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    static void deathLock(Object lock1, Object lock2) {
        try {
            synchronized (lock1) {
                Thread.sleep(10);// 不会在这里死掉
                synchronized (lock2) {// 会锁在这里,虽然阻塞了,但不会抛异常
                    System.out.println(Thread.currentThread());
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
}
运行结果

Starting thread...
Interrupting thread...
Stopping application...

因为当线程1和线程2执行中断操作时,线程1在获取锁lock2过程中,而线程2在努力获取lock1过程中,第30行Thread.sleep(10)设定线程睡眠时间已过,所以此处不会抛出异常,又因为线程在获取synchronized同步锁过程的是不抛中断异常的,所以两个线程不会出现中断。

如果把30行的时间改长一些,比如Thread.sleep(1000L),结果将如下:

Starting thread...
Interrupting thread...
Stopping application...
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at demo.Test2.deathLock(Test2.java:32)
	at demo.Test2$1.run(Test2.java:9)
java.lang.InterruptedException: sleep interrupted
死锁结束,原因是两个线程还是睡眠的时候就被中断了,导致sleep()方法抛出中断异常,退出虚拟机。

可中断死锁

Example5试着去中断处于死锁状态的两个线程,但这两个线在收到任何中断信号后抛出异常,所以interrupt()方法可以中断死锁线程:

import java.util.concurrent.locks.ReentrantLock;

public class <span style="font-family: Arial, Helvetica, sans-serif;">Example5 </span><span style="font-family: Arial, Helvetica, sans-serif;">extends Thread {</span>
    public static void main(String args[]) throws Exception {
        final ReentrantLock lock1 = new ReentrantLock();
        final ReentrantLock lock2 = new ReentrantLock();
        Thread thread1 = new Thread() {
            public void run() {
                deathLock(lock1, lock2);
            }
        };
        Thread thread2 = new Thread() {
            public void run() {
                // 注意,这里在交换了一下位置
                deathLock(lock2, lock1);
            }
        };
        System.out.println("Starting thread...");
        thread1.start();
        thread2.start();
        Thread.sleep(20);
        System.out.println("Interrupting thread...");
        thread1.interrupt();
        thread2.interrupt();
        System.out.println("Stopping application...");
    }

    static void deathLock(ReentrantLock lock1, ReentrantLock lock2) {
        try {
        	lock1.lockInterruptibly();
                Thread.sleep(1);// 不会在这里死掉
                try{
                	lock2.lockInterruptibly();
                	System.out.println(Thread.currentThread());
                	
                }catch (InterruptedException e) {
                	lock2.unlock();
                }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.exit(1);
        }finally{
        	lock1.unlock();
        }
        
    }
}
运行结果:

Starting thread...
Interrupting thread...
Stopping application...
Exception in thread "Thread-1" Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
	at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
	at demo.Test2.deathLock(Test2.java:52)
	at demo.Test2$2.run(Test2.java:17)
java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
	at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
	at demo.Test2.deathLock(Test2.java:52)
	at demo.Test2$1.run(Test2.java:11)


中断I/O操作

然而,如果线程在I/O操作进行时被阻塞,又会如何?I/O操作可以阻塞线程一段相当长的时间,特别是牵扯到网络应用时。例如,服务器可能需要等待一个请求(request),又或者,一个网络应用程序可能要等待远端主机的响应。

实现此InterruptibleChannel接口的通道是可中断的:如果某个线程在可中断通道上因调用某个阻塞的 I/O 操作(常见的操作一般有这些:serverSocketChannel. accept()、socketChannel.connect、socketChannel.open、socketChannel.read、socketChannel.write、fileChannel.read、fileChannel.write)而进入阻塞状态,而另一个线程又调用了该阻塞线程的 interrupt 方法,这将导致该通道被关闭,并且已阻塞线程接将会收到ClosedByInterruptException,并且设置已阻塞线程的中断状态。另外,如果已设置某个线程的中断状态并且它在通道上调用某个阻塞的 I/O 操作,则该通道将关闭并且该线程立即接收到 ClosedByInterruptException;并仍然设置其中断状态。如果情况是这样,其代码的逻辑和第三个例子中的是一样的,只是异常不同而已。

如果你正使用通道(channels)(这是在Java 1.4中引入的新的I/O API),那么被阻塞的线程将收到一个ClosedByInterruptException异常。但是,你可能正使用Java1.0之前就存在的传统的I/O,而且要求更多的工作。既然这样,Thread.interrupt()将不起作用,因为线程将不会退出被阻塞状态。Example5描述了这一行为。尽管interrupt()被调用,线程也不会退出被阻塞状态,比如ServerSocket的accept方法根本不抛出异常。

很幸运,Java平台为这种情形提供了一项解决方案,即调用阻塞该线程的套接字的close()方法。在这种情形下,如果线程被I/O操作阻塞,当调用该套接字的close方法时,该线程在调用accept地方法将接收到一个SocketException(SocketException为IOException的子异常)异常,这与使用interrupt()方法引起一个InterruptedException异常被抛出非常相似,(注,如果是流因读写阻塞后,调用流的close方法也会被阻塞,根本不能调用,更不会抛IOExcepiton,此种情况下怎样中断?我想可以转换为通道来操作流可以解决,比如文件通道)。下面是具体实现:

class Example6 extends Thread {
    volatile ServerSocket socket;

    public static void main(String args[]) throws Exception {
        Example6 thread = new Example6();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        Thread.currentThread().interrupt();// 再调用interrupt方法
        thread.socket.close();// 再调用close方法
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("Stopping application...");
    }

    public void run() {
        try {
            socket = new ServerSocket(8888);
        } catch (IOException e) {
            System.out.println("Could not create the socket...");
            return;
        }
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Waiting for connection...");
            try {
                socket.accept();
            } catch (IOException e) {
                System.out.println("accept() failed or interrupted...");
                Thread.currentThread().interrupt();//重新设置中断标示位
            }
        }
        System.out.println("Thread exiting under request...");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值