ReentrantLock
文章目录
可重入指任意线程在获取到锁之后可以再次获取到该锁而不会被该锁阻塞。
可重入锁指支持可以重入的锁,该锁可以支持一个线程对资源的重复加锁。
线程再次获取锁问题: 锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则可以再次获取成功。
锁的最终释放问题:线程重复n此获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求对于获取进行计数自增,计数表示当前锁被线程重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示该锁成功释放。
主要成员
ReentrantLock实现了Lock接口,并在内部创建了一个内部类Sync继承了AQS类。在Sync上又延伸出公平锁和非公平锁。在绝对时间上,先对锁进行获取请求一定先获取到,则该锁是公平的,反之,锁是非公平锁。
构造函数
ReentrantLock的构造函数如下
// 无参构造函数 默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 有参构造函数 true是公平锁 false是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Sync类
sync继承了AbstractQueuedSynchronizer。主要还是依托于AQS进行锁的管理。初识AQS
存在如下方法和作用如下:
方法名 | 说明 |
---|---|
void lock(); | 获取锁,由子类实现 |
boolean nonfairTryAcquire(int acquires) | 非公平方式获取同步状态 true成功获取;false获取失败 |
boolean tryRelease(int releases) | 释放状态,true成功释放;false释放失败 |
boolean isHeldExclusively() | 判断资源是否被当前线程占有 true代表当前线程占用,false代表其他线程占用 |
ConditionObject newCondition() | 创建一个新的condition对象 |
Thread getOwner() | 或者当前占用线程,null代表为有占用者线程 |
int getHoldCount() | 返回目前占用的状态(次数) 0代表未被占用 |
boolean isLocked() | 资源是否被锁了(占用),true已被占用,false未被占用 |
void readObject(java.io.ObjectInputStream s) | 自定义反序列化逻辑 |
源码部分
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
* 获取锁的方法,由子类具体实现
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
* 非公平方式获取锁
*/
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
// 0代表目前没有线程竞争该锁
if (c == 0) {
// 通过CAS操作占用锁,占用成功的话,直接结束该方法
if (compareAndSetState(0, acquires)) {
// 将当前线程变成资源的占用者
setExclusiveOwnerThread(current);
return true;
}
}
// 当前线程就是该锁的占用者
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 对线程再次获取该锁的时候,对状态值进行自增
setState(nextc);
return true;
}
return false;
}
//试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
// 释放同步状态 true表示锁成功释放
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 当同步状态为0,说明资源以及可以释放了,清空资源占用者
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// 判断资源是否被当前线程占有
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
// 返回资源的占用线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 返回目前占用的状态
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// 资源是否被锁了(占用)
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
自定义反序列化逻辑
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
NonfairSync 非公平锁
继承了Sync,提供了非公平方式的获取方法lock()
。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
* 获取锁
*/
final void lock() {
// 直接尝试CAS占资源,如果成功就将自己变为占用者
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 锁已经被占用,或者set失败 以独占模式获取对象,忽略中断估计排队去了
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
FairSync公平锁
同样继承了Sync,提供了公平方式的获取方法lock()
和tryAcquire(int acquires)
。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 以独占模式获取对象,忽略中断估计排队去了
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
* 尝试公平获取锁
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 0代表无占用者
if (c == 0) {
// 如果当前线程位于队列的头部或队列为空,则CAS操作获取锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 成功获取锁,设置占用者
setExclusiveOwnerThread(current);
return true;
}
}
// 当前线程就是该锁的占用者
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 对线程再次获取该锁的时候,对状态值进行自增
setState(nextc);
return true;
}
return false;
}
}
示例分析
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class FairAndUnfairTest {
private static Lock fairLock = new ReentrantLock2(true);
private static Lock unfairLock = new ReentrantLock2(false);
public static void main(String[] args) throws InterruptedException {
System.out.println("非公平锁");
unfair();
System.out.println("公平锁");
fair();
}
public static void unfair() throws InterruptedException {
testLock(unfairLock);
}
public static void fair() throws InterruptedException {
testLock(fairLock);
}
private static void testLock(Lock lock) throws InterruptedException {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new Job(lock)) {
@Override
public String toString() {
return getName();
}
};
thread.setName("t_" + i);
thread.start();
}
Thread.sleep(11000);
}
private static class Job extends Thread {
private Lock lock;
public Job(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
for (int i = 0; i < 2; i++) {
// 去竞争锁
lock.lock();
try {
s();
Thread.sleep(1000);
System.out.println("获取锁的当前线程[" + Thread.currentThread().getName() + "],第" + (i + 1) + "次执行, 同步队列中的线程" + ((ReentrantLock2) lock).getQueueThreads() + "");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 当前线程释放锁后,唤醒同步队列中的首结点去获取锁(公平式),其他线程会与首结点对应的线程竞争锁(非公平)
lock.unlock();
}
}
}
private void s(){
lock.lock();
try {
System.out.println("获取锁的当前线程[" + Thread.currentThread().getName() + "],重入执行, 同步队列中的线程" + ((ReentrantLock2) lock).getQueueThreads() + "");
} finally {
// 当前线程释放锁后,唤醒同步队列中的首结点去获取锁(公平式),其他线程会与首结点对应的线程竞争锁(非公平)
lock.unlock();
}
}
}
private static class ReentrantLock2 extends ReentrantLock {
public ReentrantLock2(boolean fair) {
super(fair);
}
public Collection<Thread> getQueueThreads() {
List<Thread> threadList = new ArrayList<>(super.getQueuedThreads());
Collections.reverse(threadList);
return threadList;
}
}
}
执行结果
非公平锁
获取锁的当前线程[t_0],重入执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_0],第1次执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_0],重入执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_0],第2次执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_1],重入执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_1],第1次执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_1],重入执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_1],第2次执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_2],重入执行, 同步队列中的线程[]
获取锁的当前线程[t_2],第1次执行, 同步队列中的线程[]
获取锁的当前线程[t_2],重入执行, 同步队列中的线程[]
获取锁的当前线程[t_2],第2次执行, 同步队列中的线程[]
公平锁
获取锁的当前线程[t_0],重入执行, 同步队列中的线程[]
获取锁的当前线程[t_0],第1次执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_1],重入执行, 同步队列中的线程[t_2, t_0]
获取锁的当前线程[t_1],第1次执行, 同步队列中的线程[t_2, t_0]
获取锁的当前线程[t_2],重入执行, 同步队列中的线程[t_0]
获取锁的当前线程[t_2],第1次执行, 同步队列中的线程[t_0, t_1]
获取锁的当前线程[t_0],重入执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_0],第2次执行, 同步队列中的线程[t_1, t_2]
获取锁的当前线程[t_1],重入执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_1],第2次执行, 同步队列中的线程[t_2]
获取锁的当前线程[t_2],重入执行, 同步队列中的线程[]
获取锁的当前线程[t_2],第2次执行, 同步队列中的线程[]
- 什么是可重入,什么是可重入锁? 它用来解决什么问题?
- ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。
- ReentrantLock是如何实现公平锁的?
- ReentrantLock是如何实现非公平锁的?
- ReentrantLock默认实现的是公平还是非公平锁?
- 使用ReentrantLock实现公平和非公平锁的示例?
- ReentrantLock和Synchronized的对比?