java并发编程实战:第十三章----显示锁
一、Lock与ReentrantLock
Lock接口中定义了一种无条件、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。
1 public interfece Lock 2 { 3 void lock(); 4 void lockInterruptibly() throws InterruptedException; 5 boolean tryLock(); 6 boolean tryLock(long timeout, TimeUnit unit 7 throw InterruptedException; 8 void unlock(); 9 Condition newCondition(); 10 }
ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。ReentrantLock同样提供了可重入的加锁语义
1 Lock lock = new ReentrantLock(); 2 ... 3 lock.lock(); 4 try { 5 // 更新对象状态 6 // 捕获异常,并在必要时恢复不变性条件 7 } finally { 8 lock.unlock();//一定要记得在finally块里释放 9 }
1、轮询锁与定时锁
- 轮询锁和定时锁可由tryLock来实现
- 轮询锁,定时锁可以避免死锁的发生
- 轮询锁通过释放已获得的锁,并退回重新尝试获取所有锁(lock.tryLock()),定时锁通过释放已获得的锁,放弃本次操作(lock.tryLock(timeout, unit))来避免死锁
轮循锁
1 while (true) { 2 if (fromAcct.lock.tryLock()) { 3 try { 4 if (toAcct.lock.tryLock()) { 5 try { 6 if (fromAcct.getBalance().compareTo(amount) < 0) 7 throw new InsufficientFundsException(); 8 else { 9 fromAcct.debit(amount); 10 toAcct.credit(amount); 11 return true; 12 } 13 } finally { 14 toAcct.lock.unlock(); 15 } 16 } 17 } finally { 18 fromAcct.lock.unlock(); 19 } 20 } 21 if (System.nanoTime() < stopTime) 22 return false; 23 NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod); 24 } 25 } 26
定时锁
1 public boolean trySendOnSharedLine(String message, 2 long timeout, TimeUnit unit) 3 throws InterruptedException { 4 long nanosToLock = unit.toNanos(timeout) 5 - estimatedNanosToSend(message); 6 if (!lock.tryLock(nanosToLock, NANOSECONDS)) 7 return false; 8 try { 9 return sendOnSharedLine(message); 10 } finally { 11 lock.unlock(); 12 } 13 } 14
2、可中断的锁获取操作
- Lock.lockInterruptibly():该锁与lock相似,但可以被中断
- 如果线程未被中断,也不能获取到锁,就会一直阻塞下去,直到获取到锁或发生中断请求
- 定时的lock.tryLock(timeout, unit)同样能响应中断
3、非块结构加锁
- 内置锁是基于块结构的加锁
- Lock可以使块与块交叉实现非块结构的加锁(连锁式加锁或者锁耦合),例:链表中,next节点加锁后,释放pre节点的锁
二、性能考虑因素
竞争性能是可伸缩性的关键因素:如果有越多的资源被耗费在锁的管理和调度上,那么应用程序得到的资源就越少
在Java5.0中,ReentrantLock能提供更高的吞吐量,但在Java6中,二者的吞吐量非常接近
三、公平性
公平锁——Lock fairLock = new ReentrantLock(true);
- 在公平的锁上,线程将按照它们发出请求的顺序来获得锁
- 在非公平的锁上,则允许”插队“:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁
- 公平性将由于在挂起线程和恢复线程时存在的开销而极大地降低性能(非公平性的锁允许线程在其他线程的恢复阶段进入加锁代码块)
- 当持有锁的时间相对较长,或者请求锁的平局时间间隔较长,那么应该使用公平锁
- 内置锁默认为非公平锁
四、在Synchronized和ReentrantLock之间作出选择
在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。否则,还是应该优先使用synchronized
Synchronized是JVM的内置属性,能执行一些优化,并且基于块结构与特定栈管理,便于检测识别发生死锁。
Java6提供了一个管理和调试接口,锁可以通过该接口进行注册,从而与ReentrantLocks相关的加锁信息就能出现在转储中,并通过其他的管理接口和调试接口来访问
五、读—写ReadWriteLock锁——一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行
对于在多处理器系统上被频繁读取的数据结构,读 - 写锁能够提高性能。而在其他情况下,读 - 写锁的性能比独占锁的性能要略差一些,这是因为它们的复杂性更高
1 public interface ReadWriteLock { 2 Lock readLock(); 3 Lock writeLock(); 4 }
读写锁的可选实现:
- 释放优先。写入锁释放后,应该优先选择读线程,写线程,还是最先发出请求的线程
- 读线程插队。锁由读线程持有,写线程再等待,再来一个读线程,是继续让读线程访问,还是让写线程访问
- 重入性。读取锁和写入锁是否可重入
- 降级。将写入锁降级为读取锁
- 升级。将读取锁升级为写入锁
在非公平的锁中,线程获得访问许可的顺序是不确定的。写线程降级为读线程是可以的,当从读线程升级为写线程这是不可以的(这样会导致死锁)