慎用ReentrantLock

前言:
代码简洁与性能高效无法两全其美,本文章专注于并发程序的性能,如果您追求代码简洁,本文章可能不太适合,本文章属于 Java Concurrency in Practice读书笔记。
在java5中,新增加ReentrantLock提供了一种比synchronized更为灵活的锁机制。为啥说灵活,而不是说性能更高呢?ReentrantLock提供的锁功能跟synchronized的功能基本是一致的,就是一翻版的synchronized类。但是它支持可轮询,定时及可中断的机制,所以说它是更灵活的。为啥没说他性能更高呢?因为这个在java6及以上,性能跟synchronized的基本持平。所以说,如果你的程序运行在java6以上,那么就没有必要再使用ReentrantLock对象了。
下面我们来仔细认识一下ReentrantLock。首先,他的正确用法:
Java代码   收藏代码
  1. Lock lock = new ReentrantLock();  
  2. ...  
  3. lock.lock();  
  4. try {  
  5.     // 对锁定对象进行更新等操作  
  6.     //处理异常  
  7. finally {  
  8.     lock.unlock();  
  9. }  
 可以明显看出,它相对以下代码(synchronized写法,行数要多,控制要复杂。
Java代码   收藏代码
  1. synchronized(this){  
  2.     //更新操作  
  3. }  
 那么,ReentrantLock被保留了下来,与synchronied相比还有什么优势呢?
1、可轮询。
原书上面的例子看着比较复杂,但意思很简单。一个转账的操作,要么在规定的时间内完成,要么在规定的时间内告诉调用者,操作没有完成。这个例子就是要了ReentrantLock的可轮询特性,就是在规定的时间内,反复去试图获得一个锁,如果获得成功,就能完成转账操作,如果在规定的时间内,没有获得这个锁,那么就是转账失败。如果使用synchronized的话,肯定是无法做到的。代码:
Java代码   收藏代码
  1. public boolean transferMoney(Account fromAcct,  
  2.                              Account toAcct,  
  3.                              DollarAmount amount,  
  4.                              long timeout,  
  5.                              TimeUnit unit)  
  6.         throws InsufficientFundsException, InterruptedException {  
  7.     long fixedDelay = getFixedDelayComponentNanos(timeout, unit);  
  8.     long randMod = getRandomDelayModulusNanos(timeout, unit);  
  9.     long stopTime = System.nanoTime() + unit.toNanos(timeout);  
  10.   
  11.     while (true) {  
  12.         if (fromAcct.lock.tryLock()) {  
  13.             try {  
  14.                 if (toAcct.lock.tryLock()) {  
  15.                     try {  
  16.                         if (fromAcct.getBalance().compareTo(amount)  
  17.                                 < 0)  
  18.                             throw new InsufficientFundsException();  
  19.                         else {  
  20.                             fromAcct.debit(amount);  
  21.                             toAcct.credit(amount);  
  22.                             return true;  
  23.                         }  
  24.                     } finally {  
  25.                         toAcct.lock.unlock();  
  26.                     }  
  27.                  }  
  28.              } finally {  
  29.                  fromAcct.lock.unlock();  
  30.              }  
  31.          }  
  32.          if (System.nanoTime() < stopTime)  
  33.              return false;  
  34.          NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);  
  35.      }  
  36. }  
 2、可中断
在synchronied的代码中,进入临界区的代码是无法中断的,这个很不灵活,如果我们使用一个线程池来分发任务,如果一个代码长期占有锁肯定会影响到线程池的其他任务,因此,加入中断机制提高了对任务更强的控制性。
Java代码   收藏代码
  1. public boolean sendOnSharedLine(String message)  
  2.         throws InterruptedException {  
  3.     lock.lockInterruptibly();  
  4.     try {  
  5.         return cancellableSendOnSharedLine(message);  
  6.     } finally {  
  7.         lock.unlock();  
  8.     }  
  9. }  
  10.   
  11. private boolean cancellableSendOnSharedLine(String message)  
  12.     throws InterruptedException { ... }  
 
在讨论到锁的时候,顺便说一下公平性。在new ReentrantLock的时候,有一个构造函数是带boolean类型的。这个参数告诉ReentrantLock是构造一个公平的锁还是不公平的锁。心想,获得一个锁还要指定公平性,我当然希望使用公平的锁了。
后来才明白,原来这里的公平性是指获取锁的时候,是否允许插队。允许插队,就是创建了不公平的锁。并且,ReentrantLock默认采用的是不公平的锁。为啥采用不公平的锁呢?应该先到先得嘛。原因在于线程挂起。当多个线程同时请求一个锁时,未获得锁的线程B会被挂起,当锁被线程A释放时,刚好来了一个线程C,那么操作系统就需要选择,第一,从挂起的队列中选择一个线程B,按照先到先得的原则,将锁交给它。但是这需要很大的开销,因为那个线程B很可能正睡觉呢,或者还在做美梦呢,叫醒它还得让它热热身,等他来接锁的时候,可能黄花菜都凉了。第二种选择,就是将锁交给刚好到来的这个线程C,刚到的线C程拿到锁就能使用。为了提高性能,操作系统选择第二个选择。
说到公平性,JDK的synchronized锁也是采用的非公平锁。
综合以上认识,如果你没有要求锁有可轮询和可中断的需求,还是使用synchronized内置锁吧。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值