• 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

  • 相关阅读:
    [moka同学笔记]八、Yii2.0课程笔记(魏曦老师教程)[授权]
    [moka同学转载]Yii2 中国省市区三级联动
    [moka同学笔记]四、Yii2.0课程笔记(魏曦老师教程)[匿名函数的使用操作]
    [moka同学笔记]Linux命令基本格式及目录处理命令
    [moka同学笔记]使用composer 安装yii2以及遇到的问题
    [moka同学笔记]MySql语句整理
    [moka同学笔记]三、Yii2.0课程笔记(魏曦老师教程)关联字段增加搜索
    Android笔记:ListView
    Android笔记:去除标题栏
    Android笔记:内部类
  • 原文地址:https://www.cnblogs.com/liumy/p/11581515.html
Copyright © 2020-2023  润新知