JUC并发编程基石AQS之中断

前几篇介绍AQS源码都是基于正常流程来分析,把主要流程走了一遍,但是在看源码时发现好多逻辑都没走到,其中最重要的当属线程的中断以及节点在什么情况下会取消排队,这篇文章我们还是拿具体例子来分析一下。

中断

首先简单说一下线程的中断。**java线程中,中断只是一个状态,中断线程只是设置了中断状态,并不是立刻将线程中断停止。**响应中断是取决于你自己代码逻辑中是否会判断中断状态,如果你不判断中断状态,那么设置了中断其实也没有影响,只是告诉你线程被中断过,让开发者可以自主选择处理中断的逻辑。下面简单介绍下几个重要方法

class Thread implements Runnable {
  	/**
     * 实例方法 中断线程,其实是将线程的中断状态设置为true
     */
   	public void interrupt() {}
  
  	/**
     * 静态方法  让线程本身判断是否被中断过,返回线程的中断状态,并且重置中断位,即将中断状态设置为false
     */
  	public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
  	
  	/**
     * 实例方法 判断是否被中断过,返回线程的中断状态
     */
  	public boolean isInterrupted() {
        return isInterrupted(false);
    }
}

上面的几个方法,可以写几个例子试一下,更加直观。

不检查中断的情况

public class TestAqsInterrupt {
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        thread1.setName("thread1");
        MyThread thread2 = new MyThread();
        thread2.setName("thread2");
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }

    public static  class MyThread extends Thread {
        @Override
        public void run() {
            lock.lock();

            try{
                System.out.println(this.getName() + "获取到锁");
                while (true) {

                }
            }finally {
                lock.unlock();
            }
        }
    }
}

上面代码没有任何逻辑,就是模拟两个线程争抢锁,获取到锁的线程会死循环,之后主线程睡眠2秒后中断中断未获得锁的线程,我们来看分析下未获得锁的线程接下来的执行情况。

场景:

thread1获取到锁进入死循环。thread2争抢锁失败加入到等待队列,并且调用了LockSupport.park(this);挂起。

此时主线程执行thread2.interrupt();

分析:

acquire

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

acquireQueued

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;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

此时thread2挂起在parkAndCheckInterrupt方法中的LockSupport.park(this);处。

当调用thread2.interrupt();时,由于interrupt()方法内部会调用LockSupport.unpark(t),此时thread2会被唤醒继续执行,并且返回中断状态true(thread2.interrupt()方法设置)。回到acquireQueued方法中的15行,设置interrupted = true;

此时thread2继续执行acquireQueued中的for循环,由于thread1死循环正在占用锁,仍然获取不到锁继续挂起。所以我们是否设置了中断,其实对于thread2是没有影响的,只是会让他唤醒一次,**如果获取不到锁会继续排队。**这就是不检查中断的情况。

检查中断状态

public class TestAqsInterrupt {
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        thread1.setName("thread1");
        MyThread thread2 = new MyThread();
        thread2.setName("thread2");
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }

    public static  class MyThread extends Thread {
        @Override
        public void run() {
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
            try{
                System.out.println(this.getName() + "获取到锁");
                while (true) {

                }
            }finally {
                lock.unlock();
            }
        }
    }
}

相较于上一个例子,只是将加锁的方法由lock.lock();改为lock.lockInterruptibly();即检查中断。

场景:

thread1获取到锁进入死循环。thread2加入到等待队列,并且调用了LockSupport.park(this);挂起。

此时主线程执行thread2.interrupt();

分析:

acquireInterruptibly

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

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; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

此时thread2挂起在parkAndCheckInterrupt方法中的LockSupport.park(this);处。

当调用thread2.interrupt();时,由于interrupt()方法内部会调用LockSupport.unpark(t),此时thread2会被唤醒继续执行,并且返回中断状态true(thread2.interrupt()方法设置)。回到acquireQueued方法中的15行,设置interrupted = true;

thread2继续执行到doAcquireInterruptibly的16行,抛出中断异常,在抛出异常前调用finally中的cancelAcquire(node);方法(由于未获取到锁,failed为true),即取消排队。此时我们在外面catch到异常时就执行我们的异常处理就行。所以检查中断的结果就是如果线程被其他线程中断过,让线程取消排队,并且抛出中断异常。

如有不实,还望指正。

你可能感兴趣的:(java基础)