ReentrantLock
在Java中通常使用锁有两种方式,一种是使用synchronized关键字,另一种就是Lock接口下的子类,ReentrantLock就是Lock的默认实现之一。ReentrantLock是可重入锁,也是独占锁。弥补synchronized的局限性,提供更加灵活的加锁方式。
1、常见方法
- 获取锁
/** 获取锁 */
void lock();
/** 如果当前线程未被中断,则获取锁 */
void lockInterruptibly();
/** 仅在调用时锁为空闲状态才获取锁 */
boolean tryLock();
/** 如果锁在给定的等待时间内空闲,并且线程未被中断,则获取锁,弱国超过等待时间则获取锁失败,返回false */
boolean tryLock(long timeout, TimeUnit unit);
- 释放锁
/** 释放锁 */
void unlock()
其中最常用的就是lock()和unlock(),使用lock时需要手动释放锁,一般用try…catch包住业务代码,在finally里释放锁。
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
System.out.println(".........");
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
2、锁类型
ReentrantLock支持公平锁和非公平锁,默认的构造非公平锁(无参构造函数),而构造公平锁采用有参构造函数,传入Boolean类型的true 【new ReentrantLock(true)】。
-
公平锁
按照线程等待时间来排序,等待时间越久的先被执行。内部说白了就是一个链表(等待队列),每次释放锁后,从链表头部开始取。
-
非公平锁
非公平锁发生在对象A.unlock()同时,对象B里面想要获取锁,这时候对象A还没有通知等待队列中的线程,就被对象B抢先占用了锁。
非公平锁在调用lock后,首先就会调用CAS进行一次枪锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回。而在CAS失败后,和公平锁一样都会进入tryAcquire()方法,在该方法中,如果锁释放了(state=0),非公平锁就会直接CAS枪锁,但是公平锁会判断等待队列是否有线程处在等待状态,没有则不去枪锁。相对来说非公平锁性能更好些,因为它的吞吐量比较大,当然非公平锁让获取锁的时间变得不确定,可能会导致在阻塞队列中的线程长期获取不到锁。
3、与synchronized比较总结
- synchronized是独占锁,加锁和解锁的过程自动进行,方便操作,但不够灵活。
- ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但是灵活。
- synchronized是可重入锁,因为加锁和解锁自动进行,不必担心最后是否释放锁。
- ReentrantLock也是可重入锁,但加锁和解锁需要手动进行,且加锁次数和解锁次数需一样,否则其他线程无法获取锁。
- synchronized不可中断,一个线程获取不到锁就会一直等着。
- ReentrantLock获取锁时,可以设置超时时间。