Java并发编程-ReentrantLock原理
api使用
static ReentrantLock lock = new ReentrantLock(true);//true 公平锁,false非公平锁
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
lock.lock(); //手动加锁
todo();//执行任务
lock.unlock();//手动解锁
}
}).start();
}
public static void todo(){
System.out.println("--------------todo------");
}
AQS(同步器)原理:
通过独占锁来分析同步器原理。
AQS:队列同步器用例构建锁。使用的是一个int成员变量,来表示同步状态,查看AbstractQueuedSynchronizer类的源码如下:
getState() 获取当前同步状态
setState() 设置当前同步状态
compareAndSetState() 使用CAS设置当前状态,改方法能够保证状态设置的原子性。
同步队列的基本结构:
没有成功获取同步状态的线程会成为节点加入该队列的队尾。同步队列的基本结构:
如图所示:同步队列基本结构包含了两个节点类型引用,一个指向头节点,另一个指向尾节点。
假设当一个线程成功获取了同步状态,其他线程无法获取到同步状态,会被构建成节点并加入同步队列中,而这个加入队列过程中必须保证线程安全,因此同步器提供了基于CAS的设置节点方法:compareAndSetTail(Node expect, Node update)。
static final class Node { //节点类
volatile int waitStatus;//
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
如下代码:重要是通过tryAcquire方法获取同步状态,如果获取失败则构造同步节点,并通过addWaiter方法加入同步队列队尾,最后通过acquireQueued方法使得该节点已“死循环”的方式获取同步状态。
此代码重要是是否能获取同步锁,如果获取失败则加入同步队列尾部,(同步锁获取,节点构造,加入同步队列,在同步队列中自旋)
public final void acquire(int arg) {
if (!tryAcquire(arg) && // tryAcquire 获取同步状态
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//Node.EXCLUSIVE代表独占锁
selfInterrupt();
}
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)) {//使用compareAndSetTail保证加入同步队列
pred.next = node;
return node;
}
}
enq(node);//通过“死循环”来保证节点正确添加
return node;
}
//通过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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //加锁
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//阻塞当前线程,
return Thread.interrupted();
}
//
private Node enq(final Node node) {
for (;;) {//通过“死循环”来保证节点正确添加,
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
同步器将节点加到同步队列的过程如图:节点加入到同步队列
同步队列遵循FIFO(先进先出),首节点是获取同步成功的节点,首节点在释放同步状态时,将会唤醒后继节点,而后继节点在获取同步状态成功时,并设置为首节点。该过程如下:首节点的设置
如图所示:设置首节点是通过获取同步状态成功的线程来完成的,需要将首节点设置为原节点的后继节点并断开首节点的next的引用即可。
如图所示:节点自旋获取同步状态:acquireQueued
加锁过程
总结:
解锁过程
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)//队尾0 其他为-1
unparkSuccessor(h); //叫醒其他的节点
return true;
}
return false;
}
顶顶顶顶顶顶顶顶顶
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
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);//唤醒处于阻塞状态的线程thread
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
public native void unpark(Object var1); //使用os底层的指令。