Lock和ReentrantLock
public interface Lock{
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long timeout,TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
Lock提供的是一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。
使用Lock时,必须在finally块中释放锁。否则,在被保护的代码中抛出了异常,那么这个锁永远都无法释放。
Lock Lock = new ReentrantLock();
lock.lock();
try{
} finally {
lock.unlock();
}
轮询锁和定时锁
可定时的和可轮询的锁获取模式是由tryLock方法实现的。在内置锁中,死锁是一个严重的问题,恢复程序的唯一方法是重新启动程序,而防止死锁的唯一方法就是在构造程序时避免出现不一致的锁顺序。可定时的和可轮询的锁提供了另外一种选择:避免死锁的发生。
如果不能获得所有需要的锁,那么可以使用可定时的或可轮询的锁获取方式,从而使你重新获得控制权,它会释放已经获得的锁,然后重新尝试获取所有锁(或者至少会将这个失败记录到日志,并采取其他措施)。
public boolean transferMoney(Account from,Account to,DollarAmount amount,long timeout,TimeUnit unit) throws InsufficientFundsException,InterruptedException {
long fixedDelay = getFixedDelayComponentNanos(timeout,unit);
long randMod = getRandomDelayComponentNanos(timeout,unit);
long stopTime = System.nanoTime() + unit.toNanos(timeout);
while(true){
if(from.lock.tryLock()){
try{
if(to.lock.tryLock()){
try{
if(from.getBalance().compareTo(amount) < 0){
throw new InsufficientFundsException();
}else{
from.debit(amount);
to.credit(amount);
return true;
}
} finally {
to.lock.unlock();
}
} finally {
from.lock.unlock();
}
}
if(System.nanoTime < stopTime){
return false;
}
NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
}
}
带时间限制的加锁
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();
}
}
PS:不先加锁直接解锁:
public class ReentrantLock_learning {
private static Lock lock = new ReentrantLock();
public static void main(String[] args){
try {
} finally {
lock.unlock();
}
}
}
java.lang.IllegalMonitorStateException
可中断的锁获取操作
public boolean sendOnSharedLine(String message) throws InterruptedException{
lock.lockInterruptibly();
try{
return cancellableSendOnSharedLine(message);
} finally {
lock.unlock();
}
}
private boolean cancellableSendOnSharedLine(String message) throws InterruptedException{
...
}
非块结构的加锁
在内置锁中,锁的获取和释放都是基于代码块的。而降低锁的粒度能提高代码的可伸缩性。
性能考虑因素
java5.0中,从单线程到多线程内置锁的性能将急剧下降,但在java6.0中对内置锁进行了优化,内置锁的性能不会由于竞争而急剧下降。
公平性
在ReentrantLock的构造函数中提供了两种公平性选择:创建一个非公平的锁或公平的锁。在公平锁上,线程将按照他们发出请求的顺序来获得锁,但在非公平的锁上,允许“插队”。
在激烈竞争的情况下,非公平锁额性能高于公平锁的性能的一个原因是:在恢复一个被挂起的线程和该线程真正开始运行之间存在着严重的延迟。
在synchronized和ReentrantLock中选择
在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的、可中断的锁获取操作,公平队列,以及非块结构的锁。
ReentrantLock的非块结构特性仍然意味着获取锁的操作不能和特定的栈帧关联起来,而内置锁却可以。在线程转储时有一定区别。
读-写锁
ReentrantLock实现了一种标准的互斥锁:每次最多只有一个线程能持有ReentrantLock。但对于维护数据的完整性,互斥通常是一种过于强硬的加锁规则,因此也就不必要的限制了并发性。
读/写锁:一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行。
public interface ReadWriteLock{
Lock readLock();
Lock writeLock();
}
在读取锁和写入锁之间的交互可以采用多种实现方式。ReadWriteLock中的一些可选实现包括:
- 释放优先。当一个写入操作释放写入锁时,并且队列中同时存在读线程和写线程,那么应该优先选择读线程,写线程还是最先请求的线程?
- 读线程插队。如果锁是由读线程持有,但有写线程等待,那么新到达的读线程能否立即获得访问权,还是应该在写线程后等待?如果允许读线程插队到写线程之前,那么将提高并发性,但却可能造成写线程饥饿的问题。
- 重入性。读锁和写锁能否重入?
- 降级。如果一个线程持有写入锁,那么它能否在不释放该锁的情况下获得读锁?这可能会使写入锁被降级为读取锁,同时不允许其他写线程修改被保护的资源。
- 升级。读锁能否优先于其他正在等待的读线程和写线程而升级为一个写入锁?在大多数的读-写锁实现中并不支持升级,因为如果没有显式的升级操作,那么很容易造成死锁。(如果两个读线程同时升级为写入锁,那么两者都不会释放读锁)
ReentrantReadWriteLock在构造时也可以选择是否为公平锁。在公平锁中,等待时间最长的线程将优先获取锁。如果这个锁由读线程持有,而另一个线程请求写入锁,那么其他读线程都不能获得读取锁,直到写线程使用完并且释放了写入锁。在非公平的锁中,线程获得访问许可的顺序是不确定的。写线程降级为读线程是可以的,读线程升级写线程是不可以的。
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 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();
}
}
}