建议去B站先看下子路老师的视频
ReentrantLock源码
- synchronized通过在对象头的markword进行操作从而实现互斥锁
- ReentrantLock通过将线程加入AQS阻塞队列从而实现同步互斥锁
首先初始化一个ReentrantLock
ReentrantLock lock = new ReentrantLock(true);
这个时候是默认的构造函数,新建一个公平锁
public ReentrantLock(boolean fair){
sync = fair ? new FairSync() : new NonfairSync();
}
然后加锁操作lock.lock()
,这个方法会去调用FairSync下面的lock()
方法
final void lock(){
acquire(1);
}
接着我们进入这个acquire(1)
去看看,原来它是AQS的一个方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先第一个判断是tryAcquire(arg)
,我们应该去它的子类,也就是Sync
去看,这个时候也就是去FairSync
类的tryAcquire(arg)
去看
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
AQS源码(自旋+CAS+park/unpark)
AQS(AbstractQueuedSynchronizer)类的设计主要构成
// 队首
private transient volatile Node head;
// 队尾
private transient volatile Node tail;
// 锁状态
private volatile int state;
// 持有锁的那个线程(这个属性其实是在AbstractOwnableSynchronizer类里的)
private transient Thread exclusiveOwnerThread;
AQS中的入队操作源码解析
addWaiter
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 直接入队,然后返回
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// enq使第一个线程入队
enq(node);
return node;
}
enq
// 通过这个函数我们可以看出AQS中的head一直都是指向一个空的Node节点
private Node enq(final Node node) {
// 若当前队列为空,那么for循环会执行两次,第一次执行if(将head指向一个空的Node),第二次执行else(将当前的node连接到上个空Node之后,也就是入队)
for (;;) {
Node t = tail;
// 第一次会执行if(将head指向一个空的Node)
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
}
// 第二次会执行else(将当前的node连接到上个空Node之后,也就是入队)
else {
// 连接到上一个空Node,即入队
node.prev = t;
// 将队列尾节点指向当前节点node
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
入队后的队列结构如下图所示:注意,队列头为空节点,代表的是当前获得锁的线程。也可以理解为持有锁的线程永远不会在队列里(保证持有锁的线程不参与排队的原则),比如这个时候t1拿到锁了,那么t1会被置位head,并且里面的值全为null(见acquireQueued方法中的setHead方法)。
多线程非交替执行下。比如t1线程获取了ReentrantLock锁,这个时候t2线程来了,对t2的处理流程图如下图所示:
总结
ReentrantLock
比synchronized
快,是因为前者的加锁操作是在JDK的工作层次下操作的,而后者需要调用操作系统去工作。
ReentrantLock
中一部分操作是在JDK的层面下解决的,还有一部分也是在操作系统中解决的;- 多线程交替执行(不会交叉,比如线程t1执行完了t2才开始执行,这个时候和队列没有关系):比如上面贴出的AQS中的
acquire
方法,首先是执行tryAcquire
这个方法是不会调用操作系统的,如果这个方法返回true了,那就不会再继续判断后面调用操作系统的的acquireQueued
方法 - 多线程不是交替执行的:这个时候就会AQS中的队列就不为空了。
- 多线程交替执行(不会交叉,比如线程t1执行完了t2才开始执行,这个时候和队列没有关系):比如上面贴出的AQS中的
synchronized
在JDK1.6之前全都要经过操作系统来解决,所以那个时候提出了ReentrantLock
ReentrantLock.lockTnterruptibly()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;