为什么只有lockInterruptibly可以被interrupted,而lock和synchronized不行

来源:互联网 发布:作图软件有哪些 编辑:程序博客网 时间:2024/05/21 10:25
问题起源:在看可重入锁的时候无意中阅读到一句话“synchronized与Lock在默认情况下是不会响应中断(interrupt)操作,会继续执行完。lockInterruptibly()提供了可中断锁来解决此问题”
本文作用:将使用代码验证这样一种说法,并从源码的角度来分析为什么。

先看三段大同小异的代码
代码一:可证lockInterruptibly()能响应interrupt操作

public class InterruptionTest {
    static Lock lock = new ReentrantLock();

    static class innerClass implements Runnable{
        Lock lock;

        public innerClass(Lock lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " is running");
                lock.lockInterruptibly();
                System.out.println(4);
                System.out.println(Thread.currentThread().getName() + " is over");
            } catch (Exception e) {
                System.out.println("haha");
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        innerClass innerClass = new innerClass(lock);
        Thread thread = new Thread(innerClass);
        try {
            lock.lock();
            thread.start();
            System.out.println(1);
            TimeUnit.SECONDS.sleep(3);
            thread.interrupt();
            System.out.println(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


結果:
1
Thread-0 is running
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
    at com.liuen.threads.InterruptionTest$innerClass.run(InterruptionTest.java:27)
    at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
    at com.liuen.threads.InterruptionTest$innerClass.run(InterruptionTest.java:34)
    at java.lang.Thread.run(Thread.java:745)
2
haha

代码二:可证lock()不能被interrupt
将代码一种加粗代码更换为:lock.lock()

结果:
1
Thread-0 is running
2
4
Thread-0 is over


代码三:可证synchronized同样不响应interrupt代码
public class InterruptionTest {

    static class innerClass implements Runnable{

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " is running");
                synchronized (this){
                    System.out.println(4);
                    System.out.println(Thread.currentThread().getName() + " is over");
                }
            } catch (Exception e) {
                System.out.println("haha");
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        innerClass innerClass = new innerClass();
        Thread thread = new Thread(innerClass);
        try {
            synchronized (innerClass){
                thread.start();
                System.out.println(1);
                TimeUnit.SECONDS.sleep(3);
                thread.interrupt();
                System.out.println(2);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:
1
Thread-0 is running
2
4
Thread-0 is over

以上三段代码确实证实了“synchronized与Lock在默认情况下是不会响应中断(interrupt)操作,会继续执行完。lockInterruptibly()提供了可中断锁来解决此问题 ”
接下来我们来分析一下lockInterruptibly()的几段源码

DOC中的原文:
If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until one of two things happens:
- The lock is acquired by the current thread; or
- Some other thread interrupts the current thread, and interruption of lock acquisition is supported   //其他线程打断了当前线程,并且当前是支持中断锁的

package java.util.concurrent.locks;
//lock接口中的lockInterruptibly() 抛出了打断异常
void lockInterruptibly() throws InterruptedException;

这个方法被多个类实现了,这里选取ReentrantLock里面的实现来分析

package java.util.concurrent.locks;
//sync是ReentrantLock内部类Sync的实例,继承自AQS
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);          //虽然Sync重写了AQS的部分方法,但是acquireInterruptibly()并没有被重写,而是直接用了AQS中的代码
}

接下来就可以看见AQS中这段代码的庐山真面目了

public final void acquireInterruptibly(int arg)          //acquireInterruptibly  这个方法其实有必要着重说一下,可以看看AQS对acquire的源码,其目的就是为了获取锁
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();          //立马看当前线程是否被打断,如果被打断了则直接抛出异常。这两行代码在lock()方法中是没有的
    if (!tryAcquire(arg))                          //这个tryAcquire(arg)方法是AQS中最重要的方法,各个锁在实现不同策略是基本都需要重写此方法。因为分析的ReentrantLock,在ReentrantLock中,其内部
        doAcquireInterruptibly(arg);               //在实现公平锁和非公平锁的时候都重写了这个方法。这里主要看一下后面doAcquireInterruptibly()
}

doAcquireInterruptibly( ) 这个方法在AQS内部,并且并没有被任何子类重写。“没有被任何子类重写”这点很重要,
因为要知道AQS的doc描述:Provides aframework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues

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())                              //这里shouldParkAfterFailedAcquires用来判断是否阻塞当前节点,parkAndCheckInterrupt来执行阻塞,下面一段代码会贴出parkAndCheckInterrupt
                throw new InterruptedException();                    //如果看过ReentrantLock里其他代码的应该不会对这段代码陌生。但是这里的抛出打断异常,缺失lock()方法整个实现过程中都没有的
        }                                                            //执行到这里是应为前两部都返回了真,也就是当前线程确实被打断,那么紧接着就抛出打断异常。到此即是原因                        
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);                                        //调用LockSupport阻塞当前线程
    return Thread.interrupted();                                   //阻塞结束时判断是顺利被前继线程唤醒,还是被打断
}

插一句:wait()和sleep()都会响应打断操作
原创粉丝点击