你真的了解线程中断吗?

何为中断?

计算机的世界里处处都有中断,任何工作都离不开中断,可以说整个计算机系统就是由中断来驱动的。那么什么是中断?简单来说就是CPU停下当前的工作任务,去处理其他事情,处理完后回来继续执行刚才的任务,这一过程便是中断!

响应中断与非响应中断的区别

每个线程都有一个boolean类型的中断状态,当中断线程时,这个线程的中断状态将被设置为true,在Thread中包含了中断线程以及查询线程中断的方法,如下:

public class Thread{
  public void interrupt(){.......}
  public boolean isInterrupted(){......}
  public static boolean interrupted(){......}
}
复制代码

interrupt()方法能中断目标线程,而isInterrupted()方法能返回目标线程的中断状态。静态的interrupted()方法将清除当前的中断状态并返回它之前的值,这也是清除中断状态的唯一方法。

调用interrupt()方法并不意味着立即停止目标线程正在执行的工作,而只是传递了请求中断的消息。

下面我们来看一个例子来理解下这3个方法:

public class InterruptTest {

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

        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean isInterrupted = Thread.currentThread().isInterrupted();
                System.out.println(isInterrupted);
                if (isInterrupted) {

                    System.out.println("hao");
                }

                System.out.println(Thread.currentThread().getName() + " threadOne isInterrupt1 " + Thread.currentThread().isInterrupted());
                while (!Thread.interrupted()){
                    System.out.println("--------");
                }
                System.out.println(Thread.currentThread().getName() + " threadOne isInterrupt2 " + Thread.currentThread().isInterrupted());
            }
        }, "t1");
        //启动线程
        threadOne.start();
        //设置中断标志
        threadOne.interrupt();
        System.out.println("interrupt 执行完毕。。。。。");
        threadOne.join();
        System.out.println("main over... ");
        System.out.println(Thread.currentThread().getName() + " isInterrupted3:" + threadOne.isInterrupted());
    }
}
复制代码

输出结果:

interrupt 执行完毕。。。。。
true
hao
t1 threadOne isInterrupt1 true
t1 threadOne isInterrupt2 false
main over... 
main isInterrupted3:false
复制代码

线程执行的顺序不一样打印的结果也不一样!

false
t1 threadOne isInterrupt1 false
--------
--------
--------
--------
--------
--------
--------
interrupt 执行完毕。。。。。
t1 threadOne isInterrupt2 false
main over... 
main isInterrupted3:false
复制代码

第三种输出结果

false
t1 threadOne isInterrupt1 true
t1 threadOne isInterrupt2 false
interrupt 执行完毕。。。。。
main over... 
main isInterrupted3:false
复制代码

main线程打印的 isInterrupted3一直都是false,因为没有线程去中断main线程,这里先忽略,只看t1线程;好好揣摩一下上面3种打印的结果就能明白这3个方法的作用;

当线程在调用wait(),sleep(),和join()方法的时候,这个时候如果收到中断请求(调用了interrupt()方法)或者在开始执行时发现某个已被设置好的中断状态时,将抛出一个异常!

public class InterruptTest {

    @SneakyThrows
    public static void main(String[] args) {

        Thread threadOne = new Thread(new Runnable() {
            @Override
            @SneakyThrows
            public void run() {
                while (true) {
                    System.out.println("+++++++++++");
                    Thread.sleep(3000);
                    System.out.println("----------");
                }
            }
        }, "t1");

        InterruptTest t = new InterruptTest();
        threadOne.start();
        Thread.sleep(200);
        threadOne.interrupt();
    }
}
复制代码

打印结果:

+++++++++++
Exception in thread "t1" java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at cn.haoxy.use.lock.reenlock.InterruptTest$1.run(InterruptTest.java:25)
	at java.lang.Thread.run(Thread.java:748)
复制代码

上面我们对Thread中的interrupt(),isInterrupted(),interrupted()有所了解了;下面我们看看什么是响应中断和非响应中断。

AQS中的响应中断与非响应中断

其实上面抛出异常就是响应中断的一种方式;我们以Lock为例:

 public static void main(String[] args) {

        Lock lock = new ReentrantLock();
        //这里先加锁的目的是 让下面的lock.lockInterruptibly()方法能进入到doAcquireInterruptibly()方法中
        lock.lock();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    //注意这里使用的是lockInterruptibly()方法-响应中断
                    lock.lockInterruptibly();
                    System.out.println("lockInterruptibly....");
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + " interrupted.");
                    e.printStackTrace();
                }
            }
        }, "t1");
        t1.start();
        Thread.sleep(1000);
   			//调用interrupt()方法将其中断
        t1.interrupt();
        Thread.sleep(5000);
    }
复制代码

打印结果

t1 interrupted.
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at cn.haoxy.use.lock.reenlock.InterruptTest$3.run(InterruptTest.java:92)
	at java.lang.Thread.run(Thread.java:748)
复制代码

这里调用了lock.lockInterruptibly()尝试去获取锁,在尝试获取锁的过程中调用了t1.interrupt()方法将t1线程设置中断标志,跟踪源码当代码来到doAcquireInterruptibly()方法中:

 private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; 
                    failed = false;
                    return;
                }
             		// 1  判断是否真的需要挂起,如果需要挂起就会调用parkAndCheckInterrupt()方法将其挂起
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
复制代码

这里重点是1处的代码shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt(),其他代码我们已经在JUC AbstractQueuedSynchronizer(AQS)源码-加解锁分析中详细的讲解了,这里就不在过多的赘述!

因为在调用lock.lockInterruptibly()方法之前在main线程中调用了lock.lock()方法,所以这里是要将线程挂起等待的。所以执行了parkAndCheckInterrupt()方法中的LockSupport.park(this)方法将线程挂起!挂起不是我们的重点,我们的重点是在挂起之后醒来的时候,整个main方法执行结束,lock.lock()结束,主线程释放锁,这个时候就会轮到线程t1去获取锁;t1线程就会在它挂起的地方醒来:

   private final boolean parkAndCheckInterrupt() {
     		//挂起的地方
        LockSupport.park(this);
     		//醒来继续向下执行
        return Thread.interrupted();
    }
复制代码

当醒来的继续向下执行Thread.interrupted(),在执行Thread.interrupted()方法的时候t1.interrupt()方法已经执行了,也就是中断t1线程;Thread.interrupted()返回true;那接下来就是throw new InterruptedException();t1线程就被中断获取锁了;这就是Lock响应中断获取锁的过程;

有响应中断,当然也会有非响应中断:

   public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

		
    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; 
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

复制代码

如果你看过我前面的文章你对上面的代码肯定很眼熟!

这个和响应中断获取锁的不同的地方在:

//不响应中断
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
      interrupted = true;
}         
//响应中断
  if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
       throw new InterruptedException();
  }
     
复制代码

不响应中断就是简单的设置一下标志位让 interrupted = true;

但是这里有一个问题一直也在困扰我,将 interrupted = true也就是acquireQueued(final Node node, int arg)方法返回了true,那接下来就会执行selfInterrupt();方法;

   static void selfInterrupt() {
     		//再次将当前线程设置为中断状态
        Thread.currentThread().interrupt();
    }
复制代码

selfInterrupt();方法中调用了interrupt()方法;目的是什么呢?我个人觉得因为在线程醒来的时候在parkAndCheckInterrupt()方法里面调用了Thread.interrupted()方法将线程的标志改变了,当调Thread.interrupted()虽然返回了true,但是将中断标志改为了false,所以要在selfInterrupt()方法中调用Thread.currentThread().interrupt()将当前线程设置为中断状态(复位);也就是还原用户的标志,用户判断线程状态自行决定怎么处理;

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享