先说说ReentrantLock与传统的有锁编程方法的区别,再来看ArrayBlockingQueue中是怎么使用ReentrantLock的
1.ReentrantLock可以实现公平锁和非公平锁,synchronized只能实现非公平锁
2.ReentrantLock可以在多个Condition上进行等待,唤醒或者阻塞在一个Condition上等待的对象,synchronized只依赖wait() notify() notifyAll()很难做到
3.ReentrantLock要手动释放锁,一般是在finally中释放,synchronized在退出同步语句块时自动释放锁
4.ReentrantLock可以轮询尝试获取锁,synchronized做不到
下面是ArrayBlockingQueue的源码分析
1.公平锁
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
如果以公平锁的方式进行访问,就是以请求插入/获取的先后次序来进行,反之以随机的顺序进行插入/获取。
还可以看到ArrayBlockingQueue有两个条件等待队列,notEmpty条件相当于等待从ArrayBlockingQueue取数据的一个队列,notFull条件相当于等待往ArrayBlockingQueue插入的一个队列
1.Condition vs wait() notify()
Condition相当于一个加强版的wait()和notify(),能够明确的控制多个等待队列。比如说,要写一个简单的生产者-消费者模型,用wait()和notify()是可以的,因为生产者只有两个状态:缓冲区满和缓冲区未满,缓冲区满的时候,就用wait()停止生产,缓冲区还有空间的时候,就用notify()来唤醒生产者,消费者同理也有两种状态。那如果有多种状态呢?这个时候用wait() notify()就很难实现了,这个时候Condition无疑是更好的选择,尽管这一点没有在ArrayBlockingQueue中体现(ArrayBlockingQueue相当于一个生产者消费者模型)。
以ArrayBlockingQueue的take为例,take()是阻塞的
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//当ArrayBlockingQueue为空时,让等待从Queue中取出的线程等待
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}
//实际调用的是extract()
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
//随机唤醒一个等待插入的线程
notFull.signal();
return x;
}
put()和insert()同理
3.drainTo()
drainTo()函数实际上是对从ArrayBlockingQueue获取取多个元素的优化,避免了在重复的加锁解锁
public int drainTo(Collection<? super E> c, int maxElements) {
checkNotNull(c);
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = takeIndex;
int n = 0;
int max = (maxElements < count) ? maxElements : count;
while (n < max) {
c.add(this.<E>cast(items[i]));
items[i] = null;
i = inc(i);
++n;
}
if (n > 0) {
count -= n;
takeIndex = i;
notFull.signalAll();
}
return n;
} finally {
lock.unlock();
}
}
总的思路就是如果想取的个数(maxElement)小于最大容量的话,就按想取的个数来取,否则取最大容量。只进行了一次加锁提高了效率