因为在看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时,表示该方法是一个阻塞方法,如果这个方法被中断,那么它将努力提前结束阻塞状态。
参考资料