在激烈竞争的情况下,闯入锁比公平锁性能好的原因之一是:挂起的线程重新开始,与他真正开始运行,两者之间会产生严重的延迟。我们假设线程A持有一个锁,线程B请求该锁。因为此时锁正在使用中,所以B会被挂起。当A释放锁后,B重新开始。与此同时,如果C请求这个锁,那么C得到了很好的机会获得这个锁使用它,并且甚至可能在B被唤醒前就已经释放该锁了。在这样的情况下,各方面都获得了成功,B并没有比其他任何线程晚得到锁,C更早地得到了锁,吞吐量得到了改进。
在持有锁的时间相对较长,或者请求锁的平均时间间隔比较长,那么使用公平锁是比较好的。在那些情况下,闯入带来的优势的情况——在线程正在唤醒的过程中,还没有得到锁——不太容易出现。
在内部锁不能够满足使用时,ReentrantLock才被作为更高级的工具。当你需要以下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操作,公平队列或者非块结构的锁。否则,请使用synchronized。
ReentrantLock实现了标准的互斥锁。互斥是保守的加锁策略,他避开了读读的重叠。
如果频繁的访问主要为了读取数据时,读写锁可以改进性能,在其他情况下运行的情况比独占的锁要稍微差一些,归因于他更大的复杂性。
读写锁的实现面临以下的选择:
1,释放优先。当写者释放写入锁,并且读者和写者都在队列中,应该选择哪一个获得锁?
2,读者闯入。如果锁由读者获得,但是有写者正在等待,那么新到达的读者应该被授予读取的权利么?还是应该等待?允许读者闯入到写者之前提高了并发性,但是却带来了写者饥饿的风险。
3,重进入。读取锁和写入锁允许重入吗?
4,降级。如果线程持有写入的锁,他能够再不释放该锁的情况下获得读取锁么?这可能会造成写者降级为一个读取锁,同时不允许其他写者修改这个被守护的资源。
5,升级。读取锁能够优先于其他的读者和写者升级为一个写入锁么?大多数读写的实现不支持升级,因为在没有显示的升级操作的情况下很容易造成死锁。(如果两个读者同时试图升级到同一个写入锁,并都不释放读取锁)
ReentrantReadWriteLock为两个锁提供了可重进入的加锁语义。而且可以构造为公平或非公平的。在公平锁中,选择权交给等待时间最长的线程。如果锁有读者获得,而一个线程请求写入,那么不再允许读者获得读取锁,直到写者被受理,并且已经释放了写入锁。在非公平锁中,线程允许访问的顺序是补丁的。由写者降级为读者是允许的。
public class ReadWritMap<K,V>{
private final Map<K,V> map;
private final ReadWriteLock lock=new ReentrantReadWriteLock();
private final Lock r=lock.readLock();
private final Lock w=lock.writeLock();
public ReadWriteMap(Map<K,V> map){
this.map=map;
}
public V put(K key,V value){
w.lock();
try{
return map.put(key,value);
}finally{
w.unlock();
}
}
public V get(Object key){
r.lock();
try{
return map.get(key);
}finally{
r.unlock();
}
}
}
上面的例子并没有实现
Map
,因为实现视图的方法,比如
entrySet
和
values
,是非常困难的。
void blockingAction()throws InterruptedException{
acquite lock on object state
while(precondition does not hold){
release lock
wait until precondition might hold
optionally fail if interrupted or timeout expires
reacquire lock
}
perform action
reacquire lock
}
上面的伪代码是状态依赖的可阻塞行为的结构
public abstract class BaseBoundedBuffer<V>{
private final V[] buf;
private int tail;
private int head;
private int count;
protected BaseBoundedBuffer(int capacity){
this.buf=(V[])new Object[capacity];
}
protected synchronized final void doPut(V v){
buf[tail]=v;
if(++tail==buf.length){
tail=0;
}
++count;
}
protected synchronized final V doTake(){
V v=buf[head];
buf[head]=null;
if(++head==buf.length){
head=0;
}
--count;
return v;
}
public synchronized final boolean isFull(){
return count==buf.length;
}
public synchronzied final boolean isEmpty(){
return count==0;
}
}
作为基准的类,后续会不断优化。
public class GrumpyBoundedBuffer<V> extends BaseBoundedBuffer<V>{
public GrumpyBoundedBuffer(int size){
super(size);
}
public synchronized void put(V v)throws BufferFullException{
if(isFull()){
throw new BufferFullException();
}
doPut(v);
}
public synchronized V take()throws BufferEmptyException{
if(isEmpty){
throw new BufferEmptyException();
}
return doTake();
}
}
上面的例子中,调用者必须随时捕获异常然后重试,如果代码中有多处调用put和take会比较麻烦。客户端的代码身处于自旋产生的低CPU使用率和休眠产生的弱响应性之间的两难境地。在忙等待与休眠之间的一种折中选择是调用Thread.yield。
public class SleepyBoundedBuffer<V> extends BaseBoundedBuffer<V>{
public SleepyBoundedBuffer(int size){
super(size);
}
public void put(V v)throws InterruptedException{
while(true){
synchronized(this){
if(!isFull()){
doPut(v);
return;
}
}
Thread.sleep(SLEEP_GRANULARITY);
}
}
pulbic V take()throws InterruptedException{
while(true){
synchronized(this){
if(!isEmpty()){
return doTake();
}
}
Thread.sleep(SLEEP_GRANULARITY);
}
}
}
上面的实现较为复杂,替调用者分担了负担。
条件队列的元素时等待相关条件的线程。
public class BoundedBuffer<V> extends BaseBoundedBuffer<V>{
//条件谓词:not-full{!isFull()}
//条件谓词:not-empty{!isEmpty()}
public BoundedBuffer(int size){
super(size);
}
//阻塞:直到:not-full
public synchronized void put(V v)throws InterruptedException{
while(isFull()){
wait();
}
doPut(v);
notifyAll();
}
//阻塞:直到:not-empty
public synchronized V take()throws InterruptedException{
while(isEmpty()){
wait();
}
V v=doTake();
notifyAll();
return v;
}
}
状态依赖方法的规范式
void stateDependentMethod()throws InterruptedException{
//条件谓词必须被锁守护
synchronized(lock){
while(!conditionPredicate()){
lock.wait();
}
//现在,对象处于期望的状态中
}
}
当使用条件等待时(
Object.wait
或者
Condition.await
):
1,永远设置一个条件谓词——一些对象状态的测试,线程执行前必须满足他。
2,永远在调用wait前测试条件谓词,并且从wait中返回后再次测试。
3,永远在循环中调用wait。
4,确保构成条件谓词的状态变量被锁保护,而这个锁正是与条件队列相关联的。
5,当调用wait、notify或者notifyAll时,要持有与条件队列相关联的锁
6,在检查条件谓词之后,开始执行被保护的逻辑之前,不要释放锁。
无论何时,当你在等待一个条件,一定要确保有人会在条件谓词变为真时通知你。
由于会有多个线程因为不同的原因在同一个条件队列中等待,因此不用notifyAll而是用notify是危险的,因为单一的通知容易导致同类的线程丢失全部信号。
只有同时满足下述条件后,才能用单一的notify取代notifyAll:
1,相同的等待者,只有一个条件谓词与条件队列相关,每个线程从wait返回后执行相同的逻辑。
2,一进一出,一个队条件变量的通知,至多只激活一个线程执行。
public class ThreadGate{
//条件谓词:opened-since(n){isOpen||generation>n}
private boolean isOpen;
private int generation;
public synchronized void close(){
isOpen=false;
}
public synchronized void open(){
++generation;
isOpen=true;
notifyAll();
}
//阻塞,直到:opened-since(generation on entry)
public synchronized void await() throws InterruptedException{
int arrivalGeneration=generation;
while(!isOpen&&arraivalGeneration==generation){
wait();
}
}
}
一个依赖状态的类,要么完全将它的等待和通知协议暴露(并文档化)给子类,要么完全阻止子类参与其中。
public class ConditionBounderBuffer<T>{
protected final Lock=new ReentrantLock();
//条件谓词:notFull(count<items.length)
private final Condition notFull=lock.newCondition();
//条件谓词:notEmpty(count>0)
private final Condition notEmpty=lock.newCondition();
private final T[] items=(T[])new Object[BUFFER_SIZE];
private int tail,head,count;
//阻塞,直到:notFull
public void put(T x)throws InterruptedException{
lock.lock();
try{
while(count==items.length){
notFull.await();
}
items[tail]=x;
if(++tail==items.length){
tail=0;
}
++count;
notEmpty.signal();
}finally{
lock.unlock();
}
}
//阻塞,直到:notEmpty
public T take()throws InterruptedException{
lock.lock();
try{
while(count==0){
notEmpty.await();
}
T x=items[head];
items[head]=null;
if(++head==items.length){
head=0;
}
--count;
notFull.signal();
return x;
}finally{
lock.unlock();
}
}
}
下面并不是 Semaphore 的真正实现
public class SemaphoreOnLock{
private final Lock lock=new ReentrantLock();
//条件谓词:permitsAvailable(permits>0)
private final Condition permitsAvailable=lock.newCondition();
private int permits;
SemaphoreOnLock(int initialPermits){
lock.lock();
try{
permits=initialPermits;
}finally{
lock.unlock();
}
}
//阻塞,直到:permitsAvailable
public void acquire()throws InterruptedException{
lock.lock();
try{
while(permits<=0){
permitsAvailable.awati();
}
--permits;
}finally{
lock.unlock();
}
}
public void release(){
lock.lock();
try{
++permits;
permitsAvailable.signal();
}finally{
lock.unlock();
}
}
}