ReentrantLock可重入锁
1.1 概述
ReentrantLock 实现了 Lock接口,Lock接口中定义了 lock与 unlock相关操作,并且还存在 newCondition方法,表示生成一个条件。
public class ReentrantLock implements Lock, java.io.Serializable
ReentrantLock
是一个可重入锁。
可重入锁 指的是可以再次获取自己的内部锁,一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其想要再次获取这个对象的锁的时候还是可以获取;如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1,获取多少锁就要释放多少锁,当锁的同步状态为 0 时才能释放锁。
-
为什么会造成死锁呢?
该线程本拥有这个对象的锁,当想要再次获得的时候,如果不可重入,那么就会一直等待,程序未执行完成,本身含有的锁也不会解锁,那么也就是无谓的等待,造成死锁。
1.2 代码层面
Reentrantlock
需要手动上锁与解锁,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成,并且只能在代码块中使用。
public void test () throw Exception {
ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
} finally {
// 需手动释放锁
lock.unlock();
}
}
-
ReentrantLock
可以指定是公平锁还是非公平锁-
公平锁就是某个线程释放锁之后,等待队列中的线程是按FIFO的原则获取锁,即先等待的线程先获得锁
-
非公平锁即某个线程释放锁之后,等待队列中的线程共同竞争锁,谁先得到锁就是谁的
-
相对来说,非公平锁会有更好的性能,吞吐量比较大。但非公平锁让线程获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态,即迟迟抢不到锁,无法继续进行下去。
-
-
公平锁与非公平锁的区别
- 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候锁没有被占用,那么直接获取到锁返回了
- 非公平锁在 CAS 失败后,和公平锁一样都会进入到
tryAcquire
方法,在tryAcquire
方法中,如果发现锁这个时候被释放了,非公平锁会直接 CAS 抢锁,而公平锁会判断前方是否有线程在等待,如果有,则自动加入队尾 - 如果这两次 CAS 都不成功,那么非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
ReentrantLock
默认情况是非公平的,可以通过 ReentrantLock
类的构造方法ReentrantLock(boolean fair)
来指定是否是公平,true为公平。
1.3 synchronized
与 Reentrantlock
的区别
-
共同点:两个都是可重入锁
-
synchronized
依赖于 JVM,Reentrantlock
依赖于APIsynchronized
底层实现由 JVM 实现上锁与解锁,锁升级过程也由JVM实现;能够修饰方法,运用于代码块和实例方法、静态方法Reentrantlock
需要手动上锁与解锁,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成,且只能在代码块中使用
-
ReentrantLock
比synchronized
增加了一些功能-
等待可中断 :
ReentrantLock
提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()
来实现这个机制,即正在等待的线程可以选择放弃等待,改为处理其他事情 -
可实现公平锁 :
ReentrantLock
可以指定是公平锁还是非公平锁,而synchronized
只能是非公平锁 -
可实现选择性通知:
1、
synchronized
关键字与wait()
和notify()
/notifyAll()
方法相结合可以实现等待/通知机制2、
ReentrantLock
类当然也可以实现,但是需要借助于Condition
接口(含await()、signalAll() 、 signa()方法)
与newCondition()
方法。ReentrantLock
类结合Condition
实例可以实现“选择性通知”:线程对象可在指定的Condition实例中注册,进行有选择性的线程通知。可以把多个Condition实例理解为一个个小组,小组内的信息只通知该小组内的线程。3、
synchronized
关键字就相当于整个 Lock 对象中只有一个Condition
实例,所有的线程都注册在它一个身上。如果执行notifyAll()
方法的话就会通知所有处于等待状态的线程,这样会造成很大的效率问题,而Condition
实例的signalAll()
方法 只会唤醒注册在该Condition
实例中的所有等待线程。
-