1. Lock 和 ReentrantLock
Lock 接口:
1 2 3 4 5 6 7 8
   | public interface Lock {     void lock();     void lockInterruptibly() throws InterruptedException;     boolean tryLock();   	boolean tryLock(long time, TimeUnit unit) throws InterruptedException;   	void unlock();   	Condition newCondition(); }
  | 
 
Lock 提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的。
Lock 的实现必须提供具有与内部加锁相同的内存可见性的语义。
ReentrantLock 实现了 Lock 接口,提供了与内置锁 synchronized 相同的互斥和内存可见性保证。获得 ReentrantLock 的锁与进入 synchronized 块有着相同的内存语义,释放 ReentrantLock 锁与退出 synchronized 块有相同的内存语义。
synchronized 内置锁局限性:
- 不能中断那些正在等待获取锁的线程。
 
- 在请求锁失败情况下,会无限等待。
 
Lock 锁必须在 finally 中释放。如果 Lock 锁守护的代码在 try 块之外跑出了异常,它将永远不会被释放。ReentrantLock 不能完全替代 synchronized,忘记释放 Lock 是非常危险的,因为当程序的控制权离开了守护的块时,不会自动清除锁。
1.1 可轮询的和可定时的锁请求
可定时的与可轮询的锁获取模式是由 tryLock() 方法实现的,与无条件的锁获取模式相比,它具有更完善的错误恢复机制。
 
在内置锁中,死锁是一个严重的问题,恢复程序的唯一方法是重新启动程序,而防止死锁的唯一方法就是在构造过程时避免不一致的锁顺序。
 
可定时的与可轮询的锁提供了另一种选择:避免死锁的发生。
 
在第10章,有这样一个例子:转账
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
   | import java.util.concurrent.atomic.AtomicInteger;
  public class DynamicOrderDeadlock {          public static void transferMoney(Account fromAccount,                                      Account toAccount,                                      DollarAmount amount)             throws InsufficientFundsException {                  synchronized (fromAccount) {                          synchronized (toAccount) {                                  if (fromAccount.getBalance().compareTo(amount) < 0) {                     throw new InsufficientFundsException();                 } else {                                          fromAccount.debit(amount);                     toAccount.credit(amount);                 }             }         }     }
      static class DollarAmount implements Comparable<DollarAmount> {
          private int amount = 0;
          public DollarAmount(int amount) {             this.amount = amount;         }
                   public DollarAmount add(DollarAmount d) {             this.amount = this.amount + d.amount;             return this;         }
                   public DollarAmount subtract(DollarAmount d) {             this.amount = this.amount - d.amount;             return this;         }
                   public int compareTo(DollarAmount dollarAmount) {             return Integer.compare(this.amount, dollarAmount.amount);         }
          public int getAmount() {             return amount;         }     }
      static class Account {         private DollarAmount balance;         private final int acctNo;         private static final AtomicInteger sequence = new AtomicInteger();
          public Account() {             acctNo = sequence.incrementAndGet();         }
                   void debit(DollarAmount d) {             balance = balance.subtract(d);         }
                   void credit(DollarAmount d) {             balance = balance.add(d);         }
          DollarAmount getBalance() {             return balance;         }
          public void setBalance(DollarAmount balance) {             this.balance = balance;         }
          int getAcctNo() {             return acctNo;         }     }
      static class InsufficientFundsException extends Exception {     } }
   | 
 
对于使用内置锁 synchronized 的 transferMoney() 方法:
- 如果【线程A】获取了 Account1 的锁,向 Account2 转账。
 
- 而【线程B】又获取了 Account2 的锁,向 Account1 转账。
 
- 那么就会出现【线程A】等待 Account2 的锁,【线程B】等待 Account1 的锁 —- 线程死锁
 
轮询锁:通过 tryLock 来避免锁顺序死锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
   | import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock;
  import static java.util.concurrent.TimeUnit.NANOSECONDS;
  public class DeadlockAvoidance {     private static Random rnd = new Random();
      public boolean transferMoney(Account fromAccount, Account toAccount, DollarAmount amount,                                  long timeout, TimeUnit unit)             throws InsufficientFundsException, InterruptedException {
          long fixedDelay = getFixedDelayComponentNanos(timeout, unit);         long randMod = getRandomDelayModulusNanos(timeout, unit);         long stopTime = System.nanoTime() + unit.toNanos(timeout);
          while (true) {                          if (fromAccount.lock.tryLock()) {                 try {                                          if (toAccount.lock.tryLock()) {                         try {                                                          if (fromAccount.getBalance().compareTo(amount) < 0) {                                 throw new InsufficientFundsException();                             } else {                                                                  fromAccount.debit(amount);                                 toAccount.credit(amount);                                 return true;                             }                         } finally {                                                          toAccount.lock.unlock();                         }                     }                 } finally {                                          fromAccount.lock.unlock();                 }             }             if (System.nanoTime() < stopTime) {                 return false;             }             NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);         }     }
      private static final int DELAY_FIXED = 1;     private static final int DELAY_RANDOM = 2;
           static long getFixedDelayComponentNanos(long timeout, TimeUnit unit) {         return DELAY_FIXED;     }
           static long getRandomDelayModulusNanos(long timeout, TimeUnit unit) {         return DELAY_RANDOM;     }
      static class DollarAmount implements Comparable<DollarAmount> {         public int compareTo(DollarAmount other) {             return 0;         }
          DollarAmount(int dollars) {         }     }
      class Account {         public Lock lock;
          void debit(DollarAmount d) {         }
          void credit(DollarAmount d) {         }
          DollarAmount getBalance() {             return null;         }     }
      class InsufficientFundsException extends Exception {     } }
   | 
 
定时锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
  import static java.util.concurrent.TimeUnit.NANOSECONDS;
  public class TimedLocking {     private Lock lock = new ReentrantLock();
      public boolean trySendOnSharedLine(String message, long timeout, TimeUnit unit)              throws InterruptedException {         long nanosToLock = unit.toNanos(timeout) - estimatedNanosToSend(message);                  if (!lock.tryLock(nanosToLock, NANOSECONDS)) {             return false;         }         try {             return sendOnSharedLine(message);         } finally {             lock.unlock();         }     }
      private boolean sendOnSharedLine(String message) {                  return true;     }
      long estimatedNanosToSend(String message) {         return message.length();     } }
   | 
 
1.2 可中断的锁获取操作
- 正如定时锁的获得操作允许在限时活动内部使用独古锁,可中断的锁获取操作允许在可取消的活动中使用。
 
- lockInterruptible() 方法能够在获得锁的同时保持对中断的响应,并且由于它包含在Lock中,因此无需创建其他类型的不可中断阻塞机制。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
 
  public class InterruptibleLocking {     private Lock lock = new ReentrantLock();
      public boolean sendOnSharedLine(String message)             throws InterruptedException {         lock.lockInterruptibly();         try {             return cancellableSendOnSharedLine(message);         } finally {             lock.unlock();         }     }
      private boolean cancellableSendOnSharedLine(String message)              throws InterruptedException {                  return true;     } }
   | 
 
1.3 非块结构的锁
- 在内置锁(synchronized)中,锁的获取和释放都是基于使用内置锁的代码块的,并不用考虑内置锁对代码块的控制权是如何退出的。
 
- 虽然自动释放锁简化了程序的分析,并且避免了潜在的代码错误造成的麻烦,但是有时候需要更灵活的加锁规则。Lock就是这样一个可定制化的锁。
 
2. 对性能的考量
显式锁 ReentrantLock 要比内置锁 synchronized 提供更好的竞争性能。
 
对于锁守护的程序而言,发生锁竞争时,程序的性能是可伸缩性的关键。
  :label:可伸缩性指的是:当增加计算资源的时候(比如增加额外 CPU 数量、内存、存储器、TO 带宽),吞吐量和生产量能够相应地得以改进。
  如果很多资源都花费在锁的管理和调度上,那么留给程序的就会越少。
 
锁的实现越好,那么上下文切换会更少,在共享内存总线上发起的内存同步通信也会更少。
 
3. 公平性
ReentrantLock 构造函数:
1 2 3 4 5 6 7 8 9 10 11 12
   |  public ReentrantLock() {     sync = new NonfairSync(); }
 
 
 
 
  public ReentrantLock(boolean fair) {     sync = fair ? new FairSync() : new NonfairSync(); }
 
  | 
 
- 在公平的锁上,线程将按照它们发出请求的顺序来获得锁(对于公平锁而言,可轮询的tryLock总会闯入)
 
- 在非公平的锁上,则允许”插队“:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。
 
- 公平的锁,在线程挂起和线程恢复时(上下文切换),存在的开销,会极大的降低程序性能。
 
- 非公平的锁,允许线程在其它线程的恢复阶段进入加锁代码块。
 
- 如果持有锁的时间相对较长,或者请求锁的平均时间将额较长,那么推荐使用公平锁。
 
- 内置锁 synchronized 没有提供确定的公平性保证。
 
4. 在 synchronized 和 ReentrantLock 之间进行选择
ReentrantLock
ReentrantLock 功能性方面更全面,具有更强的扩展性
 
ReentrantLock 提供了 Condition,对线程的等待和唤醒等操作更加灵活。一个 ReentrantLock 能够有多个 Condition 实例。
  1 2 3 4
   |  public Condition newCondition() {     return sync.newCondition(); }
 
  | 
 
 
ReentrantLock 可以控制线程得到锁的顺序(公平锁、非公平锁) – fair
 
ReentrantLock 可以查看锁的状态、等待锁的线程数。可以响应中断(lockInterruptibly)。
 
ReentrantLock 提供了可轮询的锁请求(tryLock)
 
在获取 ReentrantLock 时,可以设置超时时间。
 
ReentrantLock是显式锁,需要手动释放锁,忘记释放后果非常严重。
 
synchronized
- synchronized 是在JVM层面上实现的,能够通过一些监控工具监控synchronized的锁定。
 
- synchronized 能够在代码块执行完成或异常退出时自动释放锁。
 
- 被 synchronized 保护的代码块,一旦被线程获取锁,如果不释放,别的线程要获取该锁,会一直长时间的等待,不能被中断。
 
5. 读-写锁
一个资源可以被多个读操作访问,或者被一个写操作访问,读-写 两者不能同时进行。ReadWriteLock 实现的加锁策略允许多个同时存在的读者,但是只允许一个写者。
读写锁的可选实现:
- 释放优先。写入锁释放后,应该优先选择读线程,写线程,还是最先发出请求的线程
 
- 读线程插队。锁由读线程持有,写线程在等待,再来一个读线程,是继续让读线程访问,还是让写线程访问
 
- 重入性。读取锁和写入锁是否可重入
 
- 降级。将写入锁降级为读取锁
 
- 升级。将读取锁升级为写入锁
 
用读写锁包裝的 Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
   | import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
  public class ReadWriteMap <K,V> {     private final Map<K, V> map;     private final ReadWriteLock lock = new ReentrantReadWriteLock();     private final Lock readLock = lock.readLock();     private final Lock writeLock = lock.writeLock();
      public ReadWriteMap(Map<K, V> map) {         this.map = map;     }
      public V put(K key, V value) {         writeLock.lock();         try {             return map.put(key, value);         } finally {             writeLock.unlock();         }     }
      public V remove(Object key) {         writeLock.lock();         try {             return map.remove(key);         } finally {             writeLock.unlock();         }     }
      public void putAll(Map<? extends K, ? extends V> m) {         writeLock.lock();         try {             map.putAll(m);         } finally {             writeLock.unlock();         }     }
      public void clear() {         writeLock.lock();         try {             map.clear();         } finally {             writeLock.unlock();         }     }
      public V get(Object key) {         readLock.lock();         try {             return map.get(key);         } finally {             readLock.unlock();         }     }
      public int size() {         readLock.lock();         try {             return map.size();         } finally {             readLock.unlock();         }     }
      public boolean isEmpty() {         readLock.lock();         try {             return map.isEmpty();         } finally {             readLock.unlock();         }     }
      public boolean containsKey(Object key) {         readLock.lock();         try {             return map.containsKey(key);         } finally {             readLock.unlock();         }     }
      public boolean containsValue(Object value) {         readLock.lock();         try {             return map.containsValue(value);         } finally {             readLock.unlock();         }     } }
   |