java并发编程实践学习(13 ) 显示锁

来源:互联网 发布:微信域名检测 php源码 编辑:程序博客网 时间:2024/05/21 06:16

一.Lock和ReentrantLock

与内部加锁机制不同,lock提供了无条件的,可轮询的,定时的,可中断的锁获取操作,所有的加锁和解锁的方法都是显示的。
lock的锁更加复杂:锁必须在finally中释放,另一个方面,如果锁守护的代码在try块之外抛出了异常,它永远不会被释放;如果对象能够被置于不一致的状态,可能需要额外的try-catch,或try-finally。

1.可轮询的和可定时的锁请求

可定时的和可轮询的锁获取模式,是由tryLock方法实现。与无条件的锁获取相比它它具有更完善的错误恢复机制。内部锁中唯一的恢复方法是重行启动程序,唯一的构建方法是构建程序时不要出错。可轮询和可定时的锁提供了另一个锁选择:可以避免死锁的发生。
如果你不能获取所有需要的锁,那么使用可定时的和可轮询的获取方式使你能够重新拿到控制权,它会释放你已经获得的这些锁,然后再重新尝试。(至少会记录这个失败,或采取其他措施)。
对于那些具有时间限制的活动,当它们调用了阻塞方法,定时锁能够在时间预算内设定响应的超时。如果活动在时间内没能获得结果,这个机制使程序能够提前返回。使用内部锁一旦开始请求,锁就不能停止了。

2.可中断的锁获取操作

可中断的锁获取操作允许在可取消的活动中使用。当你正在响应中断的是,lockInterruptibly方法使你能获得锁,并且由于它是内置Lock的,因此你不必在创建其他种类不可中断的阻塞机制。
使用tryLock避免顺序死锁

public boolean transferMoney(final Account fromAccount, final Account toAccount, final DollarAmount amount,long timeout, TimeUnit unit) throws InterruptedException {      long fixedDelay = 123;      long randMod = 456;      long stopTime = System.nanoTime() + unit.toNanos(timeout);      while (true) {          if (fromAccount.getLock().tryLock()) { // 如果不能获得锁就返回重试              try {                  if (toAccount.getLock().tryLock()) {                      try {                          if (fromAccount.getBalance().compareTo(amount) < 0) {                              throw new RuntimeException();                          } else {                              fromAccount.debit(amount);                              toAccount.credit(amount);                          }                      } finally {                          toAccount.getLock().unlock();                      }                  }              } finally {                  fromAccount.getLock().unlock();              }          }                 if (System.nanoTime() > stopTime) { // 重试了指定次数仍然无法获得锁则返回失败              return false;          }          Thread.sleep(fixedDelay + new Random().nextLong() % randMod);      }  }  

使用预定时间的锁

public boolean trySendOneSharedLine(String message, long timeout, TimeUnit unit) throws InterruptedException {      long nanosToLock = unit.toNanos(timeout) - estimatedNanosToSend(message);      if (lock.tryLock(nanosToLock, TimeUnit.NANOSECONDS)) {          return false;      }      try {          return sendOnSharedLined(message);      }finally {          lock.unlock();      }  }  

可中断的锁获取请求

public boolean sendOnSharedLine(String message)throws InterruptedException{    lock.lockInterruptibly();    try{        return cancellableSendOnSharedLine(message);    }finally{        lock.unlock();    }}private boolean cancellableSendOnSharedLine(String message)throws InterruptedException{    ...}

3.非块结构的锁

在内部锁中,获取和释放这样的行为是块结构的-总是在其获得的相同的基本程序块中释放,而不考虑控制权是如何退出阻塞块的。
在链表中,我们可以通过每个链表节点应用分离锁来减小锁的粒度,给定节点的锁守护链接的指针,所以要遍历或修改链表,我们必须得到这个锁,并持有它直到我们获得了下一个锁;这之后我盟才能释放前一个锁。这项技术被称作连式锁,或者锁联接。

二.对性能的考量

当ReentrantLock被加入到java5.0时它提供的竞争上的性能要远远优于内部锁。

1.公平性

ReentrantLock构造函数提供了俩种公平性选择:创建非公平锁(默认)或者公平锁。
线程按顺序请求获得公平锁,非公平锁允许“闯入”:当请求到这样的锁时,如果锁的状态变为可用,线程的请求可以在等待线程的队列中向前跳跃获得该锁。(Semaphore同样提供了公平的和非公平的获取顺序)。

4.在synchronized和ReentrantLock之间进行选择

内部锁相比于显示锁有很大的优势。它更加简洁。ReentrantLock绝对是最危险的同步工具。内部锁与ReentrantLock相比,还有另外一个优点:线程转储能够显示哪些个调用框架获得了哪些锁,并能够识别发生了死锁的那些线程。
java中提供了管理和调试接口,可以使用这个接口进行注册,并通过其他管理和调试接口,从线程转储中得到ReentrantLock的加锁信息。
未来的性能改进可能更倾向于synchronized;因为它是内置于JVM的,它能够进行优化。

在内部锁不能足够使用时,ReentrantLock才被作为更高级的工具使用。

5.读写锁

ReentrantLock实现了标准互斥锁;一次最多只有一个线程能够持有相同ReentrantLock。但是互斥锁过分的限制了并发性。
读写锁:一个资源能够被多个读者访问,或者被一个写者访问,两者不能同时进行。与Lock一样ReadWriteLock允许多种实现,造成了性能、调度保证、获取优先、公平性、以及加锁语义方面不尽相同。
在频繁读的情况下读写锁能够改进性能,在其他情况下允许的情况下比独占锁要稍差一些,这归根于它更大的复杂性。
读取和写入的互动有多种实现。ReadWriteLock的一些实现选择如下:

  • 释放优先。当写者释放写入锁,并且读者和写者都排在队列中,应该选择哪一个,读者写者,还是先请求的那个?
  • 读者闯入。如果锁由读者获得,但有写者正在等待,那么写到达的写者应该被授予读取的权利么?还是应该等待?允许读者闯入到写者之前提高了并发性,但是却带来了写者饥饿的问题。
  • 重进入。读取锁和写入锁允许重进入吗?
  • 降级。如果线程持有写入的锁,它能够在不释放锁的情况下获取读取的锁吗?这可能造成写者“降级”为一个读取锁,同时不允许其它写着修改这个被守护的资源
  • 升级。读取锁能够优先于其他的读者和写者升级为一个写入锁么?大多数读写锁时间并不支持升级,因为在没有显示的升级的操作的情况下,很容易造成死锁。(如果俩个读者同时试图升级到同一个写入锁,并不释放读取锁)。

ReentrantReadWriteLock为俩个锁提供了可重进入的加锁语义。它能够被构造为非公平(默认)或者是公平的。在公平的锁中,选择权交给等待时间最长的线程;如果锁由读者获得,而一个线程请求写入锁,那么不再允许读者获得读取锁。直到写者被受理,并且已经释放了写入锁,在非公平锁中线程允许访问顺序是不定的。由写者降级为读者是允许的;从读者升级为写者是不允许的。
用读写锁包装的map

public class ReadWriteMap <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 void put(K key, V value){          w.lock();          try{              return map.put(key, value);          }finally{              w.unlock();          }      }//remove(), putAll(), clear()也完全类似      public V get(Object key){          r.lock();          try{              return map.get(key);          }finally{              r.unlock();          }      }//对于其他的只读Map方法也完全类似  }  
0 0