在Java5.0之前,在协调对共享对象的访问时可以使用的机制有synchronized和volatile.Java5.0增加了一种新的机制:ReentrantLock. ReentrantLock并不是一种替代内置加锁的方法,而是当内置锁机制不适合时,作为一种可选择的高级功能.
一,显示锁Lock和内置锁synchronized
Lock接口定义了一组抽象的加锁操作.与内置加锁机制不同的是,Lock提供了一种无条件的,可轮询的,定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的.
- 1,Lock接口如下
1, void lock()
获取锁。
2, void lockInterruptibly()
如果当前线程未被中断,则获取锁。
3, Condition newCondition()
返回绑定到此 Lock 实例的新 Condition 实例。
如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
4, boolean tryLock()
仅在调用时锁为空闲状态才获取该锁。
5, boolean tryLock(long time, TimeUnit unit)
如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
6, void unlock()
释放锁。
-
2,ReentrantLock和内置锁的对比
- 1)ReentrantLock提供了与内置锁相同的互斥性和内存可见性
- 2)和内置锁一样,ReentrantLock提供了可重入的加锁语义
- 3)Lock提供了一种无条件的,可轮询的,定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的,加锁方式更加灵活
-
3,为什么要创建和内置锁如此相似的显式锁?
- 1)无法中断一个正在等待获取锁的线程
- 2)内置锁必须在获取该锁的代码中释放,这就简化了编码工作.但是却无法实现非阻塞结构的加锁规则
- 3)在内置锁中,防止死锁的唯一办法是在构造程序时避免出现不一致的锁顺序.可定时的与可轮询的锁提供了另外一种选择:避免死锁的发生
- 4)非块结构的加锁
在内置锁中,锁的获取和释放等操作都是基于代码块的-----释放锁的操作总是与获取锁的操作处于同一个代码块. 例如ConcurrentHashMap使用的分段锁技术,降低锁的粒度提高代码的可伸缩性就必须要使用非块结构的加锁----Lock
4,内置锁和显示锁的性能
在Java5.0,ReentrantLock能提供更高的吞吐量.
但在Java6.0中,二者的吞吐量非常接近-
5,ReentrantLock的公平性.
ReentrantLock的公平性是指线程是否按照他们发出请求的顺序来获取锁在ReentrantLock的构造函数中提供了两种公平选择:创建一个非公平的锁(默认)或者一个公平的锁.在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许"插队":当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁;
我们为什么不希望所有的锁都是公平的呢?
当执行加锁操作时,公平性将由于在挂起线程和恢复线程时存在的开销而极大地降低性能.
但是,如果当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,这种情况下,"插队"带来的吞吐量提升则可能不会出现,那么这时候应该选用公平锁.
-
6, 在Synchronized和ReentrantLock之间如何选择?
- 1)与显式锁相比,内置锁更简单,简洁紧凑.仅当内置锁不能满足需求时,才可以考虑使用ReentrantLock
-2)在java5.0中,内置锁与ReentrantLock相比还有另一个有点:在线程转储中能给出在那些调用帧中获得了那些锁,并能够检测和识别发生死锁的线程.而ReentrantLock则不能.在Java6.0后才支持
-3)未来更可能提升内置锁而不是显式锁的性能.因为内置锁(synchronzied)是JVM的内置属性,他能执行一些优化,例如对线程封闭的锁对象的锁消除优化,通过增加锁的粒度来消除内置锁的同步.而如果通过基于类库的锁来实现这些功能,则可能性不大.
- 1)与显式锁相比,内置锁更简单,简洁紧凑.仅当内置锁不能满足需求时,才可以考虑使用ReentrantLock
总之,虽然与内置锁相比,显示的lock更能提供一些扩展功能,有着更高的灵活性. 但ReentrantLock不能完全替代synchronzied,只有synchronized不能满足需求的时候,才应该用他.
二,读写锁ReadWriteLock
Lock readLock()
返回用于读取操作的锁。
Lock writeLock()
返回用于写入操作的锁。
- 在读-写锁实现的加锁策略中,允许多个读操作同时进行,但每次只允许一个写操作.
- ReentrantReadWriteLock为这两种锁都提供了可重入的加锁语义.
- ReentrantReadWriteLock在构造时也可以选择一个非公平的锁(默认)和公平的锁.在公平的锁中,等待时间最长的线程将优先后的锁.如果这个锁由读线程持有,而另一个线程请求写入锁,那么其他读线程都不能获得读取锁,知道写线程使用完并且释放了写入锁.(可以认为写优先)
在非公平的锁中,线程获得访问许可的顺序是不确定的.写线程降级为读线程时可以的,但是从读线程升级为写线程时不可以的(这样会导致死锁);
一个简单的读写锁示例---使用读写锁包装一个Map,让其同步
public class ReadWriteMap<K,V>{
private Map<K,V> map;
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ReadLock readLock = lock.readLock();
private WriteLock writeLock = lock.writeLock();
public ReadWriteMap(Map<K,V> map){
this.map = map;
}
public V put(K key, V value){
try{
writeLock.lock();
return map.put(key, value);
}finally{
writeLock.unlock();
}
}
public V get(K key){
try{
readLock.lock();
return map.get(key);
}finally{
readLock.unlock();
}
}
}
参考:
<<java编发编程实战>>