ReentrantLock的加锁原理是根据一个变量的值来确定是否可以加锁,这个变量值就是state
1:ReentrantLock默认实现的是非公平锁
2: ReentrantLock是可重入锁
3:后面进来的线程没有获取到锁,就会使用LockSupport.park();方法阻塞当前线程,并把这个线程添加到队列中去
4:队列不是在ReentrantLock初始化的时候就初始化的,而是在需要队列存放元素的时候在初始化
5:在释放锁的时候就会唤醒队列的头节点元素,让头节点的线程继续执行
ReentrantLock reentrantLock = new ReentrantLock();
查看它的构造函数,默认是实现非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
其实在实例化ReentranLock是有一个boolean fair的参数的
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
如果为true就是公平锁,false是非公平锁.
接下来看下最主要的一个方法 lock()方法
public void lock() {
sync.lock();
}
继续跟进sync.lock()方法
可以看到一个抽象类Sync,抽象类一般都是有具体实现的,所以我们可以知道lock的具体实现就在Sync类当中。 在Sync的lock()方法可以知道Sync有二个子类,FairSync和NonfairSync,也就是公平锁类和非公平锁类。首先看下公平锁的lock()方法实现
final void lock() {
acquire(1);
}
继续调用了acquire方法,并传进了一个int值等于1.
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这里有二个判断,首先看下第一个条件,跟进tryAcquire()方法,但是这个方法在AQS类中并没有实现。所以可以知道是在子类中自己实现了
protected final boolean tryAcquire(int acquires) {
//得到当前线程
final Thread current = Thread.currentThread();
//获取到state的值,reentranLock就是根据state这个值来判断是否可以持有锁的
int c = getState();
//如果等于0
if (c == 0) {
//当前线程会尝试获取锁,而不是一定能得到锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//设置成独占锁
setExclusiveOwnerThread(current);
return true;
}
}
//如果当前线程就是之前获取锁的线程,也就是本身
else if (current == getExclusiveOwnerThread()) {
//这里也就是重入锁的实现,将state的值 加 1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//设置state的值
setState(nextc);
return true;
}
return false;
}
}
现在难点来了,我们看下!hasQueuedPredecessors()这个判断,里面就返回一个boolean的值
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
首先看下第一个条件 h != t,就是头节点不等于尾节点,现在有三种情况 1):队列没有初始化:那么 h != t == false,不成立,直接返回false,但是在条件判断前面加 反 ,所以!hasQueuedPredecessors()为true,然后再尝试获取锁,如果成功获取到锁,那么就直接上锁了,如果没有成功获取到锁那么tryAcquire()方法直接返回false。现在又要回到下面这个方法了啊
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
就是当队列没有初始化而且当前线程也没有获取到锁,那么就要执行后面的判断了,先看看addWaiter方法,这个方法就是当队列没有初始化就初始化一个队列,队列已经初始化了就将新元素添加到队列中,并维护好。
private Node addWaiter(Node mode) {
//将当前对象封装成一个Node对象
Node node = new Node(Thread.currentThread(), mode);
//这个是队列的尾节点,如果队列初始化了那么 tail是绝对不会为空的
Node pred = tail;
if (pred != null) {
//如果队列已经初始化了,那么直接维护该链表,将该线程设置到队列的尾部
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//这个方法就是初始化队列的
enq(node);
return node;
}
private Node enq(final Node node) {
//这里是个死循环
for (;;) {
Node t = tail;
//因为队列没有初始化,所以t == null,
if (t == null) { // Must initialize
//然后创建一个新的节点,并且头节点和尾节点都是它本身,执行完if,因为是个死循环,所以会再次执行这个判断,但是在第二次执行的时候尾节点已经不为空了,因为第一次循环已经创建了一个节点了,但是这个节点是个空对象,并不是我们传进来的node
if (compareAndSetHead(new Node()))
tail = head;
} else {
//第二次循环就是将我们传进来的node添加到尾部,现在队列中就有二个node节点了,第二个才是我们自己的节点元素,最后返回
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
addWaiter方法返回当前节点元素,然后执行acquireQueued()方法
final boolean acquireQueued(final Node node, int arg) {
//这是个标记
boolean failed = true;
try {
//这也是个标记
boolean interrupted = false;
//这里是个死循环
for (;;) {
//获取该节点的上一个节点,因为我们知道队列的第一个节点并不是排队的,第二个才算是要排队的,就好比你去窗口买票,第一个人是正在买票的,不是正在等待的,第二个人才算是排队的第一个人
final Node p = node.predecessor();
//如果上一个节点是头节点,也就是持有锁的那个对象,该节点会有点不甘心,会再次尝试获取锁,如果成功获取到锁,那么将自己设置成头节点,然后退出该循环
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//执行到这里有二种情况,1:上个节点不是头节点。2:上一个节点是头节点,但是获取锁失败了。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
看下shouldParkAfterFailedAcquire这个方法做了什么
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//节点的默认值就是为 0,所以 ws = 0,所以会走esle模块,并且将上一个节点的waitStatus的值设置成 -1,然后返回false
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
返回false之后,我们知道这个判断直接返回false了,所以会再次执行一次循环,也就是上面的acquireQueued()方法
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
然后继续执行,又进入shouldParkAfterFailedAcquire方法,但是这次waitStatus的值等于 -1,所以会执行上面的第一个if语句块,然后返回true。接着就会执行parkAndCheckInterrupt方法,这个方法就是为了将当前线程阻塞。让持有锁的线程独占。这就是整个加锁的过程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
这是公平锁实现加锁,现在来看看非公平锁加锁过程
final void lock() {
//这里少了个判断,也就是非公平锁在来新线程的时候不会排队,直接尝试获取锁,如果获取到了就持有锁,没获取到自然也是要去排队
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
公平锁:所有的线程都要排队,先来先执行
非公平锁:新线程会直接尝试获取锁,获取到了就持有锁,也就不会按顺序来排队
这就是我理解的公平锁跟非公平锁的区别
以上就是加锁过程,现在来看看解锁的过程,也就是lock.unlock()方法,跟进unlock方法,知道会执行release()方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
首先看下tryRelease()方法
protected final boolean tryRelease(int releases) {
//首先获取state的值,然后 -1
int c = getState() - releases;
//判断当前线程是不是持有锁的线程,不是直接抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//-1之后等于0,就释放锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//最后重新设置state的值,因为其它线程要根据这个值来判断是否可以获取锁的
setState(c);
return free;
}
会继续判断头节点会不会为空,不为空且waitStatus != 0 ,在加锁的过程中我们知道都把这个值设置成 -1了,所以会执行unparkSuccessor()方法
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
获取下一个节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//如果下一个节点不为空,就唤醒,然后下一个节点就会尝试加锁,然后执行任务
if (s != null)
LockSupport.unpark(s.thread);
}
以上就是个人观看源码所理解的AQS锁机制。加锁和释放锁的整个过程。有错误欢迎提出。(在初始化队列最好用笔画一下,我也是按照源码一步一步画出来才理解队列的初始化,队列初始化理解不清楚后面看起来会比较吃力)