公平锁流程及源码详解
如果这个方法返回true,那么获取锁成功;直接跳出执行这个线程的代码;
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取当前线程
int c = getState();
//获取当前锁状态
if (c == 0) {//如果当前锁状态是0,也就是没有人持有锁那么尝试去获取锁
if (!hasQueuedPredecessors() &&
//因为是公平锁所以要判断是否有线程的等待时间比这个线程长,如果没有就下一步,如果有那么返回false
compareAndSetState(0, acquires)) {
//将当前state状态改为1,就是当前锁已经有人获取了
setExclusiveOwnerThread(current);//设置占用排它锁的线程是当前线程
return true;//返回true那么执行线程其他代码
}
}
else if (current == getExclusiveOwnerThread()) {//如果当前线程是本身就拥有锁,那么锁重入
int nextc = c + acquires;//
if (nextc < 0)//如果经过这一步那么是报错了,因为按道理
throw new Error("Maximum lock count exceeded");
setState(nextc);//状态加1
return true; }//返回true获取锁成功
return false; }
将线程加入队列中
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//把当前线程作为node的一个属性,创建节点
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {//判断尾部节点是否为空,第一次进来肯定是空的不执行
//当尾部节点不为空后,节点就会在这边创建
node.prev = pred;//使得当前节点的prev指向尾部节点
if (compareAndSetTail(pred, node)) {//把尾部节点指向当前节点
pred.next = node;//空元素pred的next指向node节点,可以理解成pred是当前拥有锁的线程,因为拥有锁了,所以离开队列留下一个空壳子
return node;//结束
}
}
enq(node);//第一次进来肯定会执行这个,创建队列,如果第一次同步进来多个线程,那么都会在这个方法里面创建队列和节点,之后的节点将会在上面创建
return node;
}
在tail节点为空的时候,如果有多个线程同时进入这个节点那么他会给这些线程都创建节点,如果没有大量线程同时进入那么就仅仅会初始化头部,尾部节点,创建一个空节点(理解成当前拥有锁的节点),和当前线程的节点。
private Node enq(final Node node) {//
for (;;) {
Node t = tail;//第一次进来肯定是空的,这里可以理解成这个t是拥有锁的节点
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))//初始化头部节点
tail = head;//初始化尾部节点
} else {
node.prev = t;//在上面的方法有解释
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
获取锁失败那么要把当前线程加入队列并阻塞
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;//默认线程没有被中断过,被中断过是需要返回中断过的状态给主流程,让我们自行做处理,比如lock.lockInterruptibly()方法就会抛出异常,要我们对其做处理
for (;;) {
final Node p = node.predecessor();//获取当前节点的前一个节点
if (p == head && tryAcquire(arg)) {//判断他的前面的那个节点是否指向头部节点,如果指向了前面一个节点成立执行获取锁逻辑,上面已做解释
setHead(node);
//释放锁后,要把当前节点做为空节点指向头部节点
p.next = null; // help GC
//那么就要让当前线程的前一个节点报废,使得他的next指向空,没有了任何引用,那么自然会被gc回收
failed = false;
return interrupted;//这里返回中断信号回去,这里是false,表示该线程没有被中断过
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;//这里表示该线程被中断过,那么就需要我们对这个中断做处理,也可以选择不做处理我们的lock是不做处理的,就只是做了一个标记放在那边,我们程序员可以自身给他做处理
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//获取上一个节点的状态,当前线程的是否需要被唤醒的状态是存在上一个节点的waitStatus中的
if (ws == Node.SIGNAL)//如果上一个节点的状态是-1那么,就返回true,该节点可以在unlock()中被唤醒
return true;
if (ws > 0) {//果大于0那么就是出现异常被干掉了,正常情况下就走另一个选择
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//设置上一个节点的状态为-1,就是可唤醒的状态
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//阻塞线程
return Thread.interrupted();//返回该线程是否被中断过,如果是正常唤醒那么会返回false,如果是通过中断的方式唤醒那么会返回true
}
如果线程被中断过,设置中断标识,返回线程主流程
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
非公平锁流程及源码详解
非公平锁与公平锁的不同在于在加锁过程中,非公平锁有两次获取锁的机会,不管队列中是否有线程在等待,直接暴力获取锁返回;当加入队列后其执行方式和公平锁就一样了,只能按照队列的顺序来拿锁;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();//获取当前线程
int c = getState();//获取当前锁状态
if (c == 0) {
if (compareAndSetState(0, acquires)) {//如果状态为0直接去修改线程状态
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;
}