在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。