【Java并发】JAVA并发编程实战-读书笔记18

在激烈竞争的情况下,闯入锁比公平锁性能好的原因之一是:挂起的线程重新开始,与他真正开始运行,两者之间会产生严重的延迟。我们假设线程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();

  }

}

上面的例子中,调用者必须随时捕获异常然后重试,如果代码中有多处调用puttake会比较麻烦。客户端的代码身处于自旋产生的低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,当调用waitnotify或者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();

    }

  }

}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值