一、初识 ReentrantLock
ReentrantLock 这个是大佬 Doug Lea 写的,在早期的 java 版本中,synchronized 效率低,原因是线程的阻塞和唤醒,操作系统需要在用户态和内核态切换,线程阻塞和唤醒的代价比较高。然后大佬就看不下去了,自己写(不仅是ReentrantLock哈)。当然咯,synchronized 毕竟是亲儿子,随着时间的推移,不断对其进行优化,性能方面 synchronized 已经有了很大提升了。
首先 ReentrantLock 在 java 中是一个基础的锁对象, ReentrantLock 实现了 Lock 接口提供的一系列的基础函数,所以相比传统的 synchronized 而言,功能上更丰富,使用起来更为灵活,也更适合复杂的并发场景。在这之前先来一段代码,看看它是怎么使用的:
public class ReentrantLockTest {
//javap -p -v 对应类的.class 文件路径
private static final Lock lock = new ReentrantLock(true);
private static final Object obj = new Object();
public static void main(String[] args) {
new Thread(()->test(),"AAA").start();
new Thread(()->test(),"BBB").start();
new Thread(()->test(),"CCC").start();
new Thread(()->test(),"DDD").start();
}
public static void test() {
for(int i = 0 ; i < 20;i++) {
try{
// synchronized (obj) {
// System.out.println(Thread.currentThread().getName()+" get");
// Thread.currentThread().sleep(200);
// }
lock.lock();
System.out.println(Thread.currentThread().getName()+" get");
Thread.currentThread().sleep(150);
}catch(Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
javap -p -v 对应类的.class 文件路径 可以查看.class 文件里面的内容,这段代码是以前在别人文章中看到,刚刚突然发现了,就拿出来了,这段代码 使用 synchronized 和 lock 控制台输出的内容顺序是不一致的,有兴趣的小伙伴可以在自己的电脑上试试,不过重点不在这里,这里只是简单的看一下 ReentrantLock 怎么使用。
二、源码和API
Ⅰ、类的定义:
public class ReentrantLock implements Lock, java.io.Serializable
Ⅱ、包含的内部类
abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync
Ⅲ、Sync 类
Sync 是一个抽象类,这个类继承了 AQS,并提供了 lock 抽象方法,源码中对该类的解释为:
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
/**
* 此锁的同步控制基础。下面细分为公平和非公平版本。使用 AQS 状态标识锁的保留数
*/
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
/**
* 执行不公平的 tryLock。tryAcquire 是在子类中实现的,但是都需要对 trylock
* 方法进行不公平的尝试
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); // 获取当前线程
int c = getState(); // 获取状态值
if (c == 0) {
if (compareAndSetState(0, acquires)) { // CAS 设置状态值
setExclusiveOwnerThread(current); // 将当前线程设置为 独占模式的当前所有者
return true; // 设置成功 返回 true
}
}
else if (current == getExclusiveOwnerThread()) { // c 不为0,可能是重入这一种情况,所以获取它的所有者线程,并判断是否是当前线程对象
int nextc = c + acquires; // 是当前线程对象,更新后的状态值
if (nextc < 0) // overflow 如果更新后的值小于 0,说明重入的次数太多,导致溢出了
throw new Error("Maximum lock count exceeded"); // 抛出 Error
setState(nextc); //设置状态
return true; //返回 true
}
return false;
}
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();
}
tryRelease、newCondition、getOwner、getHoldCount、isLocked 没什么好分析的,这里就啰嗦了
Ⅳ、NonfairSync 类
同步对象的非公平锁,继承 Sync 类,并对 lock 方法进行了具体的实现
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
// 执行锁定 尝试立即执行 barge,失败后恢复到 acquire
final void lock() {
if (compareAndSetState(0, 1)) // CAS 将状态值设置为 1
setExclusiveOwnerThread(Thread.currentThread()); // 将当前线程设置为 独占模式的当前所有者
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
Ⅴ、FairSync 类
同步对象的公平锁,继承 Sync 类,并对 lock 方法进行过了具体的实现
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 公平版本的 tryAcquire.除非递归调用或没有 waiters,否则
// 不需要授予访问权限
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //获取当前线程
int c = getState(); // 获取状态值
if (c == 0) {
// 如果当前线程位于队列的开头第一个线程或队列为空 并且 CAS 设置状态成功,则进入if语句块中
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;
}
看完上面的代码后,我们便可以分析 公平锁 与 非公平锁的实现(其实就是上面的源码)
非公平锁
非公平锁的实现: 看上面 NonfairSync 类 的代码,它 lock 方法中,if 条件判断是 CAS 设置状态是否可以设置成功,
换句话说,如果此时多个线程同时设置(一次只能一个线程设置成功),那么具体哪个线程会执行成功,取决于 CPU,因为多个线程都在抢同一个资源(state),CPU 执行哪个成功,哪个就设置成功,而这个是随机无法确定的,所以这便是 非公平锁的实现。
公平锁
公平锁的实现: 看上面 FairSync 类的代码,它的 lock 方法中调用了 “acquire(1);” 方法,它的代码如下面所示,acquire方法中又调用了 tryAcquire 方法,这个方法是子类重写的方法,所以此时又会回到 子类实现中,此时便有三种情况了,第一种,获取的 state 为 0;第二种情况, state 不为 0,但独占线程和当前线程相等(即重入情况);第三,其它情况;
第一种情况:同步队列为空,这种情况 CAS 对 state 进行设置,设置成功后,并将当前线程设置为独占
第二种情况:重入
第三中情况: 在 tryAcquire 返回 false. 则 在调用 tryAcquire 的 acquire 方法中,第一个条件false 取反后为 true,执行第二个,尝试入队,
入队成功后,调用 selfInterrupt 方法,源码如下所示,中断调用该方法的 Thread 实例所代表的线程。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
三、总结
小结 |
---|
ReentrantLock 是一个可重入的锁,具有与使用 synchronized 方法和语句访问的隐式监视锁仙童的基本行为和语义,但它具有扩展功能 |
构造函数设置伪 true,在争用下,锁有利于访问最长等待的线程(可将上面代码的true改为false 运行,对比查看控制台结果) |
此类的序列化与内置锁的操作方式相同:无论其序列化时的状态如何,反序列化锁都将处于未锁定状态 |
非公平锁是通过抢占的方式来保证的 |
公平锁是通过同步队列来保证的 |