在Java1.5中引入了两种显示锁,分别是可重入锁ReentrantLock和可重入读写锁ReentrantReadWriteLock。它们分别实现接口Lock和ReadWriteLock。(注意:synchronized也是可重入锁)
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
* @return the lock used for reading
*/
Lock readLock(); /**
* Returns the lock used for writing.
* @return the lock used for writing
*/
Lock writeLock();
}
在此之前我们一直使用的是“内置锁”,也就是synchronized来保证线程安全性的。那么为什么会引入显示锁呢?毕竟synchronized用起来还是十分的简单,好用的(自动获取锁,自动释放锁,简化编程,减少错误)。但是synchronized简单好用的同时,在功能上却不足,存在局限性:
1)synchronized在申请锁如果被阻塞时,无法被中断;而Lock.lockInterruptibly()是可中断的锁;
2)synchronized无法实现轮询锁和限时锁;而使用显示锁的 Lock.tryLock() 配合 sleep() 和 while(true) 可以实现轮询锁;Lock.tryLock(long time, TimeUnit unit)实现了限时锁的功能;
3)另外显示锁的引入了,使我们可以方便实现细粒度的锁定;
4)读写锁的引入能够在读多写少的时提供更好的性能;synchronized和Lock对于“读读”环境也是互斥的;
5)另外显示锁,是我们可以使用“公平锁”;
但是,显示锁的引入也导致了编程的复杂,容易出错,容易导致没有释放锁。必须要使用try{}finally{}来保证锁被释放掉:
Lock lock = new ReentrantLock();
lock.lock();
try{
// access the resource protected by this lock
}finally{
lock.unlock();
}
if(lock.tryLock()){
try{
// ... ...
}finally{
lock.unlock();
}
}
要使用公平锁,需要传入参数:true,我们看一下他们的构造函数:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
} /**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
默认的 new ReentrantLock(); 获得的是非公平锁。new ReentrantLock(true); 获得的才是公平锁。
所谓“公平锁”是指多个线程在等待同一个锁时,按照申请的先后顺序排队,先申请的线程先获得该锁。不允许插队。但是因为“公平锁”会导致过多的线程上下文切换,从而性能不好。所以一般使用非公平锁。
synchronized 和 ReentrantLock 在“同步”的功能上是等价的。性能方面在java1.6之后也是相似的。在java1.5中显示锁有优势。
所谓的“可重入锁”,是指同一个线程在已经获得了一个对象的锁时,可以再次获得该对象上的锁,不会阻塞。实现原理是,在ReentrantLock中保存了锁的拥有者 owner 和 锁被获取的次数。
总结:
内置锁synchronized,具有简单,好用,不易出错等优点,但是在功能方面有缺陷,不灵活;而显示锁ReentrantLock功能强大,十分灵活,但是编程容易出错。性能方面在java1.6之后两者差别很小,所以在一般情况下,我们会使用内置锁synchronized,只有在synchronized不能满足我们的功能需求时,才会使用显示锁ReentrantLock。