lockInterruptibly 和lock 原理

因为在看ArrayBlockIngQueue 发现问题。其中put,take,offer(e,time,unit), poll(time,unit)是阻塞的方法,offer(e),poll(),是非阻塞方法,

 

 其中offer(e),offer(e,timeout,unit)两个方法中的获取锁的方法不一样。那为什么要这么做呢?

可以比较下面两份代码,一个用lock,一个用lockInterruptibly。 

 1 public boolean offer(E e) {
 2     checkNotNull(e);
 3     final ReentrantLock lock = this.lock;
 4     // 这里用这个lock方法。
 5     lock.lock();
 6     try {
 7         if (count == items.length)
 8             return false;
 9         else {
10             insert(e);
11             return true;
12         }
13     } finally {
14         lock.unlock();
15     }
16 }
17 
18 public boolean offer(E e, long timeout, TimeUnit unit)
19     throws InterruptedException {
20 
21     checkNotNull(e);
22     long nanos = unit.toNanos(timeout);
23     final ReentrantLock lock = this.lock;
24     //这里用这个方法
25     lock.lockInterruptibly();
26     try {
27         while (count == items.length) {
28             if (nanos <= 0)
29                 return false;
30             nanos = notFull.awaitNanos(nanos);
31         }
32         insert(e);
33         return true;
34     } finally {
35         lock.unlock();
36     }
37 }

下面来介绍一下 两个方法都有什么不同。

lockInterruptibly 优先考虑响应中断,再去获取锁。
/**
 * Acquires the lock unless the current thread is
 * {@linkplain Thread#interrupt interrupted}.
 * 获取锁,除非当前线程被打断了。 意思就是是如果不被打断,就能获取到锁。
 *
 * <p>Acquires the lock if it is not held by another thread and returns
 * immediately, setting the lock hold count to one.
 * 如果锁没有被别的线程持有,则获得这个锁,立刻返回,设置当前锁持有的个数是1.
 *
 * <p>If the current thread already holds this lock then the hold count
 * is incremented by one and the method returns immediately.
 如果当前线程已经持有锁了,那么持有锁的个数增加1,并立刻返回。
 *
 * <p>If the lock is held by another thread then the
 * current thread becomes disabled for thread scheduling
 * purposes and lies dormant until one of two things happens:
 *
 如果锁被别的线程持有,那么当前线程将出于线程调度的目的变得不可用,直到发生下面两种情况之一:
 * <ul>
 *
 * <li>The lock is acquired by the current thread; or
 *锁由当前线程获得。
 * <li>Some other thread {@linkplain Thread#interrupt interrupts} the
 * current thread.
 *或者其他线程打断当前线程
 * </ul>
 *
 * <p>If the lock is acquired by the current thread then the lock hold
 * count is set to one.
 *如果锁被当前线程获得,那么锁持有计数被设置为1.
 * <p>If the current thread:
 *
 * <ul>
 *
 * <li>has its interrupted status set on entry to this method; or
 *
 * <li>is {@linkplain Thread#interrupt interrupted} while acquiring
 * the lock,
 *
 * </ul>
 *
 * then {@link InterruptedException} is thrown and the current thread's
 * interrupted status is cleared.
 *
 如果当前线程在进入这个方法时有中断状态或者在获取锁时,被打断,则抛出InterruptedException异常,并且清除interrupted status
 * <p>In this implementation, as this method is an explicit
 * interruption point, preference is given to responding to the
 * interrupt over normal or reentrant acquisition of the lock.
 *
 在这个实现中,由于这个方法明显是一个中断点,优先考虑响应中断,而不是正常的或者可重入锁获取。
 * @throws InterruptedException if the current thread is interrupted
 */
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

/**
 * Acquires in exclusive mode, aborting if interrupted.
 * Implemented by first checking interrupt status, then invoking
 * at least once {@link #tryAcquire}, returning on
 * success.  Otherwise the thread is queued, possibly repeatedly
 * blocking and unblocking, invoking {@link #tryAcquire}
 * until success or the thread is interrupted.  This method can be
 * used to implement method {@link Lock#lockInterruptibly}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 * @throws InterruptedException if the current thread is interrupted
 */
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

/**
 * Acquires in exclusive interruptible mode.
 * @param arg the acquire argument
 */
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);
    }
}

lock() 调用了抽象类Sync(实现了AQS)的lock()方法,这是一个抽象方法,有两个实现,一个公平锁,一个非公平锁,最终都调用到acquire(int arg)方法,内部处理了中断。

优先考虑获取锁,待获取锁成功后,才响应中断。

 1  /**
 2  * 获得锁。
 3  *
 4  * 如果锁不被其他线程持有,则获取锁,并立即返回,将锁持有计数设置为1。
 5  *
 6  * 如果当前线程已经持有锁,那么持有计数将增加1,方法立即返回。
 7  *
 8  * 如果锁由另一个线程持有,则当前线程将出于线程调度目的而禁用,
 9  * 并处于休眠状态,直到获得锁,此时锁持有计数被设置为1。
10  */
11 public void lock() {
12     sync.lock();
13 }
14 /**
15   * 执行锁。子类化的主要原因是允许非公平版本的快速路径。
16 */
17 abstract void lock();
18 /**
19  * 以独占模式获取,忽略中断。通过至少调用一次{@link #tryAcquire}来实现,成功后返回。
20  * 否则,线程将排队,可能会反复阻塞和解除阻塞,调用{@link #tryAcquire}直到成功。
21  * 此方法可用于实现方法{@link Lock# Lock}。
22  *
23  * @param arg the acquire argument.  This value is conveyed to
24  *        {@link #tryAcquire} but is otherwise uninterpreted and
25  *        can represent anything you like.
26  */
27 public final void acquire(int arg) {
28     //tryAcquire(arg) 试图以独占模式获取。这个方法应该查询对象的状态是否允许以独占模式获取它,
29     //如果允许,也应该查询是否允许以独占模式获取它。
30     
31     //acquireQueued 获取队列中已存在线程的独占不可中断模式。用于条件等待方法以及获取。
32     if (!tryAcquire(arg) &&
33         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
34         //中断当前线程。
35         selfInterrupt();
36 }

比较两个方法发现:

lock优先考虑获取锁,待获取锁成功后,才响应中断。这个方法不抛出中断异常。

lockInterruptibly 优先考虑响应中断,再去获取锁。这个方法会抛出中断异常。

所以我们返回来看: 

offer(e), 这个方法的实现用是调用lock, 实现的功能是入队之后,要么返回true,要么返回false,还有如果入队的元素是null,那么会抛空指针异常,但是这个方法不会抛出被中断的异常(InterruptedException)。

这个方法内部也设置了中断状态,但是不会抛出中断异常。非阻塞方法。入队不成功,就返回。

offer(e,timeout,unit) 这个方法调用lockInterruptibly, 实现的功能是在指定的时间内如果入队成功,则返回true,反之,返回false,如果在等待入队的过程中被其他线程打断,会抛出异常。 

这个方法会抛出中断异常。而且是阻塞方法。

 2019-09-25

看 java并发编程实战,读到5.4 阻塞方法与中断方法,

看到这句话。

BlockingQueue的put和take等方法会抛出受检查异常InterruptedException,这与类库中其他一些方法的做法相同。例如Thread.sleep.

当某方法抛出InterruptedException时,表示该方法是一个阻塞方法,如果这个方法被中断,那么它将努力提前结束阻塞状态。

参考资料

https://www.jianshu.com/p/69ad22627b2b

posted @ 2019-09-24 22:15  泥古拉斯赵四  阅读(1381)  评论(0编辑  收藏  举报