目录
一、ReentrantLock和Synchronized的相似点与区别
1.AbstractQueuedSynchronizer结构
2.AbstractQueuedSynchronized相关实现类
3.AbstractQueuedSynchronizer 源码
4.AbstractOwnableSynchronizer 类源码
前言
ReentrantLock与Synchronized的作用相似,保证了同一时间只有一个线程能访问加锁的区域。
一、ReentrantLock和Synchronized的相似点与区别
1.相似点:
- 都是可重入锁:也就是说一个线程可以反复获取锁资源。
- 当线程获取ReentrantLock时,会判断state变量是否不为0(state的大小表示重入了几次),如果不为0就说明有线程获取了锁。这个时候会判断获取锁的线程是否是当前线程,如果是当前线程,则再次获取锁资源并且++state。
- 都是独占锁:同一时刻,只有一个线程可以获取锁。
2.区别:
-
锁的获取方式不同:Synchronized 是隐式锁,它的获取和释放都是由 JVM 自动控制的,不需要显式地进行操作。而 ReentrantLock 是显式锁,需要手动获取和释放锁。
-
功能上的区别:相比 Synchronized,ReentrantLock 提供了更多的功能,比如可中断锁、可超时锁、公平锁等,这些功能在某些场景下非常有用。
-
性能上的区别:在低并发的情况下,Synchronized 的性能表现通常优于 ReentrantLock。但在高并发情况下,ReentrantLock 的性能通常优于 Synchronized。
-
可见性的区别:使用 Synchronized 时,被锁定的代码块或方法中所做的修改会立即对其他线程可见。但是,使用 ReentrantLock 时,需要调用 unlock() 方法来释放锁,才能保证其他线程能够看到修改。
-
适用范围的区别:Synchronized 只适用于在单个 JVM 实例中的多线程同步,而 ReentrantLock 可以跨越多个 JVM 实例,甚至可以在分布式环境下进行线程同步。
二、ReentrantLock的三个特性的实现。
1.公平锁
公平锁:当一个新的线程来获取锁的时候,他会看看阻塞队列里面是否有线程在排队。如果有,则不去尝试获取锁,会继续排在队列后面。如果没有才会去获取锁。
非公平锁:当一个新的线程来获取锁的时候。不管阻塞队列里面是否有线程排队,都会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
ReentrantLock根据构造方法传入的参数不同,实现不同类型的锁。
无参构造方法默认是非公平锁。有参构造,传入true为公平锁。false为非公平锁。
在代码里,这两个实现类获取锁的区别则是:
公平锁:
非公平锁:
只相差了一句:hasQueuedPredecessors()
公平锁有这行代码,这行代码用来判断队列里面是否有元素。
在公平锁里,靠这句代码判断出阻塞队列没有等待的线程了,才会去尝试获取锁
2.可中断锁
可中断就是:可以根据打断(interrupt)来退出获取锁的流程。
可中断锁是依靠异常来实现的。当获取锁的途中发现线程被打断了,则会抛出异常。根据异常的特性我们知道,有异常出现的时候,不会继续执行剩下的代码。而会执行catch和finally代码块的内容。正是依靠这个特性,使得线程能依靠中断退出获取锁的过程。
使用lockInterruptibly方法就是使用可打断锁;
lockInterruptibly获取锁的主要实现是:通过for死循环来获取锁。而要跳出这个循环,只有成功获取锁或者被打断抛出异常。
3.超时锁
超时锁:当锁在约定时间内获取不到锁资源后,就会退出获取锁的流程。
作用:为了防止线程因为长时间获取不到锁而一直阻塞住,影响业务的执行。
调用tryLock方法使用超时锁
如何实现超时操作的?
1.循环获取锁的时候,每次都判断剩余时间是否足够,如果不足则放弃获取锁。
2.通过 LockSupport.parkNanos(this, nanosTimeout) 防止获取锁的途中阻塞超时
三、AbstractQueuedSynchronizer类
ReentrantLock的功能的核心就是AbstractQueuedSynchronizer类,要了解ReentrantLock就要先了解AbstractQueuedSynchronizer
ReentrantLock和AbstractQueuedSynchronizer的关联:
ReentrantLock的构造方法可以传入一个参数,根据传入的值,则会创建对应的AbstractQueuedSynchronizer实现类的对象。true为公平锁,false为公平锁。
ReentrantLock的主要方法有加锁(lock)与解锁(unlock),ReentrantLock的加锁(lock)调用的是AbstractQueuedSynchronizer的acquire方法,解锁(unlock)调用的则是realse方法
1.AbstractQueuedSynchronizer结构
AbstractQueuedSynchronizer名字是队列,但是本质是一个双向链表。有head和tail指针指向队首和对尾节点。同步器中有个Node类,Node类便是链表的节点,每个节点对应一个Node类的实例。Node有prev和next指向前后节点,waitStatus用来存节点的状态,每个节点最主要的就是存有一个线程(初始化的首节点不存线程)。也就是说,AbstractQueuedSynchronizer其实是存储线程的队列。
该队列的有一个重要的参数就是state,被volatile修饰。同步器(AbstractQueuedSynchronizer)并没有对该变量进行操作,只是提供了操作该变量的方法。而在ReentrantLock中的tryAcquire方法会操作该变量,当state等于0的时候代表没有线程持有锁,当state大于0的时候就代表有线程持有锁,而state的大小就代表该线程重入锁的次数
该队列还有个Thread参数,保存当前持有该同步器的线程。AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer类,也就是说这个Thread参数来自于AbstractOwnableSynchronizer类。
2.AbstractQueuedSynchronized相关实现类
AbstractQueuedSynchronizer类不止服务于ReentrantLock类,还有ArrayBlockingQueue、Semaphore、CountDownLatch、Executors、CyclicBarrier、Executors等等,因此该类里面有很多跟ReentrantLock不想关的代码存在,本文提取ReentrantLock类和AbstractQueuedSynchronizer类的核心代码进行解释说明。
2.acquire方法
AbstractQueuedSynchronizer的核心就是acquire方法。对于该同步器的设计思路来说,执行acquire方法就是 尝试入队,acquire方法执行完就是 出队/不需要入队。
对于ReentrantLock来说acquire方法执行完就是获取锁成功。
(1)tryAcquire方法,同步器并没有实现tryAcquire方法,而是由具体的实现类实现。
(2)addWaiter方法,实际的入队方法
(3)acquireQueued方法,入队后的节点会在一个死循环中尝试出队
(4)shouldParkAfterFailedAcquire方法和parkAndCheckInterrupt方法
3.release方法
(1)unparkSuccessor方法。realease方法判断了waitStatus!=0,说明了队列有节点需要被处理,但是该处仍未直接unpark。因为节点可能出现异常状态,需要被处理。
四 、ReentrantLock类
ReentrantLock类有三个内部类,分别是继承了AbstractQueuedSynchronizer的Sync,和继承了Sync的FairSync、NonFairSync。
Sync方法实现了FairSync和FairSync的共同逻辑,也就是tryRelease()方法。而FairSync和NonFairSync为了实现公平锁和非公平锁,则是在tryAcquire方法的实现上有细微区别。
ReentrantLock主要实现了AbstractQueuedSynchronizer的acquire()和release()方法。具体对应的就是ReentrantLock的lock()和unlock()方法
ReentrantLock默认是非公平锁,但是可以根据传入的布尔值改变
1.tryAcquire方法
在公平锁和非公平锁中,该方法的实现区别只有是否有hasQueuedPredecessors方法的这个判断。hasQueuedPredecessors方法判断队列中是否有节点在排队。在公平锁中,必须要按照先后顺序获取锁,所以需要这个判断。
state变量表示是否加了锁,如果c==0则是没加锁。那么就进行cas,把同步器中的线程设置为当前线程。也就是说,那个线程等于同步器中保存的线程,就表面那个线程获取了锁。如果c!=0的话,也不一定就不能获取锁了,因为该锁是可重入锁。如果尝试获取锁的线程等于同步器中保存的线程,那么就再把state变量加一。
2.hasQueuedPredecessors方法
判断队列里面是否有节点存在
五、测试该锁,并且附上源码
1.代码测试:
测试结果
2.ReentrantLock类源码:
package tools.myLock;
/**
* @Auther: duanYL
* @Date: 2023/10/11/14:39
* @Description:
*/
public class ReentrantLock {
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
abstract static class Sync extends AbstractQueuedSynchronizer {
//因为reentrantLock是可以重入的锁,所以tryRelease并不是一定返回true的
protected final boolean tryRelease(int releases) {
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
int newState = getState() - releases;
boolean free = false;
if (newState == 0) {
setExclusiveOwnerThread(null);
free = true;
}
setState(newState);
return free;
}
}
//类上加final有啥用
static final class NonfairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int newState = c + acquires;
if (newState < 0) {
throw new Error("Maximum lock count exceeded");
}
setState(newState);
return true;
}
return false;
}
}
static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
Thread thread = Thread.currentThread();
int state = getState();
if (state == 0) { //说明该线程是第一个竞争锁的
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(thread);
return true;
}
} else if (thread == getExclusiveOwnerThread()) {
int newState = state + acquires;
if (newState < 0) {
throw new Error("Maximum lock count exceeded");
}
setState(newState);
return true;
}
return false;
}
}
}
3.AbstractQueuedSynchronizer 源码
package tools.myLock;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.locks.LockSupport;
/**
* @Auther: duanYL
* @Date: 2023/10/11/14:51
* @Description: 该同步器的核心就是acquire方法。对于该同步器的思路来说,
* 执行acquire方法就是入队,acquire方法执行完就是出队。
*/
public class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state; //同步器并没有操作该变量,而是交于实现类来操作
// VarHandle mechanics 每个VarHandle对应一个变量,用来操作变量
private static final VarHandle HEAD;
private static final VarHandle TAIL;
private static final VarHandle STATE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class);
HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", AbstractQueuedSynchronizer.Node.class);
TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", AbstractQueuedSynchronizer.Node.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
Class<?> ensureLoaded = LockSupport.class;
}
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return STATE.compareAndSet(this, expect, update);
}
public final void acquire(int arg) {
/** 一.同步器并没有实现tryAcquire方法,而是由具体的实现类实现。
* 该方法为false表示需要入队,该方法为true表示 不需要入队 或者 出队
* 该方法就是出入队的条件,由使用者实现
* 在ReentrantLock中,tryAcquire方法保证了同一时间只有一个线程调用该方法会返回true
*
* 二.addWaiter方法 实际是将线程封装成节点,放入队列里面
*
* 三.acquireQueued方法有两种作用 1.用来出队,2.用来阻塞当前线程
* 1.acquireQueued方法会尝试一次出队,如果出队失败就进入睡眠
* 2.当线程阻塞时,release方法被调用就是一次出队信号,如果符合条件该线程会被唤醒,
* 并且满足tryAcquire的出队条件,那就出队成功
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); //只有acquireQueued返回的打断标记为true才会执行该方法,该方法重新給线程添上打断标记
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
private Node addWaiter(Node mode) {
Node node = new Node(mode); //一个node对应一个线程,创建node时,会保存当前线程信息
for (; ; ) { //死循环保证node必须入队列
Node oldTail = tail;
if (oldTail != null) { //初始的时候队列head和tail都指向null,oldTail不为空说明了初始化完成了
node.setPrev(oldTail); //这三条边的连接顺序,保证了只需要一次cas
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
/** 初始化。 该队列的本质是一个双向链表,初始化完成后head和tail都指向一个空Node,
* 空Node用来方便出入队列,以及后续操作
*/
initializeSyncQueue();
}
}
}
private boolean compareAndSetTail(Node oldTail, Node newNode) {
return TAIL.compareAndSet(this, oldTail, newNode);
}
private void initializeSyncQueue() {
Node node;
if (HEAD.compareAndSet(this, null, node = new Node())) {
tail = node;
}
}
final boolean acquireQueued(final Node node, int arg) {
/**
* 死循环保证一定能出队,循环时尝试出队一次,如果出队失败线程就park住,等待
* 伴随着循环,Node中的waitStatus的变化是0->-1
*
* 为什么这里要单独用个interrupted变量来存。
* 因为被打断的线程,如果不消除打断标记,那么tryAcquire失败,就再也不能park住了
* 而消除了打断标记就必须靠其他变量来反馈回去
*/
boolean interrupted = false;
for (; ; ) {
Node preNode = node.predecessor();
//只有头结点才允许出队,(此处的头结点并不是初始化后的空节点,而是空节点后的那个节点)
if (head == preNode && tryAcquire(arg)) {
setHead(node);
preNode.next = null;
return interrupted; //返回打断标记,表明线程是正常出队还是被打断出队的
}
if (shouldParkAfterFailedAcquire(preNode, node))
interrupted |= parkAndCheckInterrupt();
}
}
private void setHead(Node node) {
head = node;
head.thread = null;
head.prev = null;
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
/** 只修改前一个节点的值,而不修改当前节点的值
* 也就是说前一个节点的waitStatus才是当前节点的状态
*/
int waitStatus = pred.waitStatus;
if (waitStatus == Node.SIGNAL) {
return true;
} else {
pred.compareAndSetWaitStatus(waitStatus, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park();
return Thread.interrupted(); //返回线程是被打断唤醒还是正常唤醒,重置打断标记
}
private void selfInterrupt() {
Thread.currentThread().interrupt();
}
public final boolean release(int arg) {
//tryRelease方法由具体的实现类实现
if (tryRelease(arg)) {
Node node = head; //node为null说明队列还未初始化
if (node != null && node.waitStatus != 0) {
unparkSuccessor(node);
}
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
private void unparkSuccessor(Node node) {
Node s = node.next;
/**
* 此处将-1改为0,如果在分公平锁的情况下,如果被唤醒的线程获取锁失败
* 则不会直接park住,会多一次循环的机会。
* 多一次循环的机会就会降低被park的可能性
*/
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
/**
* 为什么会判断s==null,因为在真正的AbstractQueuedSynchronizer中
* 可能该线程会出现异常情况,需要将该node删除,在删除的过程中就有可能为null
*/
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev) {
if (p.waitStatus < 0)
s = p;
}
}
if (s != null) {
LockSupport.unpark(s.thread);
}
}
public final boolean hasQueuedPredecessors() {
Node h, s;
/**
* 为什么会判断s==null,因为在真正的AbstractQueuedSynchronizer中
* 可能该线程会出现异常情况,需要将该node删除,在删除的过程中就有可能为null
*/
if ((h = head) != null) {
if ((s = h.next) == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != h && p != null; p = p.prev) {
if (p.waitStatus <= 0)
s = p;
}
}
if (s != null && s.thread != Thread.currentThread())
return true;
}
return false;
}
static final class Node {
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
volatile int waitStatus; //节点的状态
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
private static final VarHandle NEXT;
private static final VarHandle PREV;
private static final VarHandle THREAD;
private static final VarHandle WAITSTATUS;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
NEXT = l.findVarHandle(Node.class, "next", Node.class);
PREV = l.findVarHandle(Node.class, "prev", Node.class);
THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
public Node() {
}
public Node(Node node) {
this.nextWaiter = node;
THREAD.set(this, Thread.currentThread());
}
public void setPrev(Node prev) {
PREV.set(this, prev);
}
final Node predecessor() {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
final boolean compareAndSetWaitStatus(int expect, int update) {
return WAITSTATUS.compareAndSet(this, expect, update);
}
}
}
4.AbstractOwnableSynchronizer 类源码
package tools.myLock;
/**
* @Auther: duanYL
* @Date: 2023/10/16/10:37
* @Description:
*/
public class AbstractOwnableSynchronizer {
protected AbstractOwnableSynchronizer() { }
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
总结
麻烦点个赞~~~