1.代码优化的设计
非公平模式下:
非公平锁的加锁流程,线程在进入同步队列等待之前有两次抢占锁的机会:
- 第一次是非重入式的获取锁,只有在当前锁未被任何线程占有(包括自身)时才能成功;
- 第二次是在进入同步队列前,包含所有情况的获取锁的方式。
只有这两次获取锁都失败后,线程才会构造结点并加入同步队列等待。
有的博客说写两次获得锁的逻辑虽然减少了代码的简洁,但这种对非重入且虽然队列中有线程在等待获取锁,但是在等待的线程在获取锁之前,这个新来的线程提前去占据了锁,提高了性能,我觉得应该是新的线程避免执行
final Thread current = Thread.currentThread();
int c = getState();//获得锁状态
这俩行代码,少执行两行代码,就算和阻塞线程同时抢占锁,因为少执行两行代,码也提高了新线程获取到锁的可能性??? 或者线程在进入等待队列前可以进行两次尝试,这能大大增加了获取锁的机会???
并且这个ReentrantLock多次用到了这个思想,入队在enq()这个方法那也用到了这种思想,以后再回来补充。
final void lock() {
if (compareAndSetState(0, 1)) //第一次CAS尝试获取锁 这行代码删了也不影响正常业务逻辑
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//第二次CAS尝试获取锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
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;
}
2.非公平模式下抢占锁
当新线程来抢占锁时,正好占据锁的线程释放了锁,虽然他会唤醒队列中阻塞的线程,但是当阻塞的线程唤醒去重新获取锁时发现锁又被占据后,由于外层循环for(::) 虽然被唤醒,但获得不了锁,又会继续陷入阻塞,只有当当前节点获得到了锁才会head才会被设置为当前节点,否则就算头节点所代表的线程释放了锁,而这时候碰巧这个锁被新来的线程所抢占,他也不会被清除,只是这个时候它表示一个空结点,下次还可以用这个head唤醒后续节点。
当新的线程来抢占锁时,如果直接CAS抢到了,提高性能的地方在于:
1>它是不需要再额外封装成Node节点来放到CLH队列中,这样节省了封装Node,入队所消耗的时间(主要原因)
2>由于就算这个新线程抢到了锁,头节点还是会唤醒阻塞队列中的线程来尝试获取锁,只是因为锁被其它线程获得,它会重新陷入阻塞罢了,但是如果公平模式下多个线程同时入CLH队列,往尾部插时还会CAS竞争tail节点,失败,浪费资源,如果其中一个获得到了锁就无需考虑这个节点入队的消耗了
3.唤醒节点时,从尾部往头节点找的原因
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
某些线程封装成节点存在CLH队列中后,但是可能存在该线程主动放弃获得资源了,轮到它是就不需要把资源给它了,所以这个从后往前找就是为了清除这些节点