ReentrantLock
是普通的java类,通过AQSAbstractQueuedSynchronizer
实现锁机制。
ReentrantLock
是一个重入锁,一个线程加锁之后,可以反复的加锁。
1.2 锁类型:
公平锁
非公平锁
reentrantLock
分为公平锁和非公平锁,可以通过构造方法来指定具体类型:
// 默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 有参构造方法,传入true,公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
默认使用的是非公平锁,非公平锁比公平多的效率更高。
1.3 获取锁
reentrantLock
的使用示例如下:
public class ReentrantLockDemo implements Runnable {
public static ReentrantLock lock=new ReentrantLock();
public static int i=0;
public void run() {
for (int j = 0; j < 10000; j++) {
try {
// 加锁
lock.lock();
i++;
}finally {
// 释放锁
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
Thread thread1 = new Thread(reentrantLockDemo);
Thread thread2 = new Thread(reentrantLockDemo);
thread1.start();
thread2.start();
thread1.join();thread2.join();
System.out.println(i);
}
}
首先看下获取锁的流程
public void lock() {
sync.lock();
}
abstract void lock();
可以看到是使用 sync
的方法,而这个方法是一个抽象方法,具体是由其子类(FairSync
)来实现的,以下是公平锁的实现:
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//获取锁,成功则true,短路与直接退出;
// addWaiter 将当前线程写入队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 获取锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//首先会判断 AQS 中的 state 是否等于 0,0 表示目前没有其他线程获得锁,当前线程就可以尝试获取锁。
int c = getState();
if (c == 0) {
// 判断 AQS 的队列中中是否有其他线程,如果有则不会尝试获取锁(公平锁独有)
if (!hasQueuedPredecessors() &&
// 如果队列中没有线程就利用 CAS 来将 AQS 中的 state 修改为1,也就是获取锁
compareAndSetState(0, acquires)) {
// 获取成功则将当前线程置为获得锁的独占线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果 state 大于 0 时,说明锁已经被获取了,则需要判断获取锁的线程是否为当前线程(ReentrantLock 支持重入),是则需要将 state + 1,并将值更新
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 将当前线程写入队列中
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;
// 将新的node设置到等待队列 队尾
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
首先判断队列是否为空,如果不为空,则利用CAS
写入队尾,如果写入失败,则需要利用enq(node)
来写入。
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;
}
}
}
}
以上部分就是自旋+CAS
一起来保证写入成功。
1.4 挂起等待线程
写入队列后需要将当前线程挂起(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);
}
}
首先会根据node.predecessor()
获取上一个节点是否为头节点,如果是,则尝试获取锁tryAcquire(arg)
,获取成功,
如果不是头结点或者获取锁失败,则会根据上一个节点的waitStatus
状态来处理(shouldParkAfterFailedAcquire(Node pred, Node node)
)
waitStatus
用于记录当前节点的状态,如节点取消、节点等待等。
shouldParkAfterFailedAcquire(p, node)
返回当前线程是否需要挂起,如果需要则调用 parkAndCheckInterrupt()
:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
利用 LockSupport
的 part
方法来挂起当前线程的,直到被唤醒.
Thread.interrupted()
检查线程是否被中断。
1.5 非公平锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
非公平锁的加锁逻辑为compareAndSetState(0, 1)
尝试获取锁,获取锁成功,将当前线程设置成获取锁的独占线程setExclusiveOwnerThread(Thread.currentThread())
。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//没有 !hasQueuedPredecessors() 判断
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;
}
非公平锁不需要尝试查看队列中是否有其他线程,而是直接尝试获取锁,
1.6 释放锁
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 释放锁
if (tryRelease(arg)) {// 释放锁成功
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒被挂起的线程
unparkSuccessor(h);
return true;
}
return false;
}
/**释放锁**/
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 当前线程是否为独占锁的线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {// 可重入锁,减到0才会完全释放锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
释放之后需要调用 unparkSuccessor(h)
来唤醒被挂起的线程
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.
*/
// // 获取头结点waitStatus
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;
// 如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点
if (s == null || s.waitStatus > 0) {
s = null;
// 就从尾部节点开始找,到队首,找到队列第一个waitStatus<0的节点。
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点unpark
if (s != null)
LockSupport.unpark(s.thread);
}
首先我们来看head节点,这里的head节点其实就是acquireQueued方法中的幸运儿,它获取了锁,获得了操作共享资源的权限,并且被置为head。到这里需要release的时候,它的使命已经完成,这时候,head只是作为一个占位的虚节点,所以需要首先将它的waitStatus
置为0这个默认值。
然后,程序将会从FIFO队列的尾节点开始搜索,找到最靠前的(非head),且waitStatus <= 0的节点。并对其进行LockSupport.unpark(s.thread);
操作,即唤醒该挂起的线程,让它起来干活。
之前挂起的线程一旦被唤醒,那么将会继续执行acquireQueued方法,进行自旋尝试获取锁,如此,便形成了一个能够良好工作的闭环。这时,拿锁、挂起、释放、唤醒都能够有条不紊,且高效地进行。
关于公平锁与非公平锁:
1 线程入AQS队列后,唤醒是有序的。
2 非公平锁指的是线程来加锁的时候,不用查看AQS队列中是否有等待线程即可以争抢锁。
2.1 AQS数据结构
AQS中基本的数据结构为Node,可以理解为一个双端链表
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
volatile Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
解释一下几个方法和属性值的含义:
方法和属性值 | 含义 |
---|---|
waitStatus | 当前节点在队列中的状态 |
thread | 表示处于该节点的线程 |
prev | 前驱指针 |
predecessor | 返回前驱节点,没有的话抛出npe |
nextWaiter | 指向下一个处于CONDITION状态的节点(由于本篇文章不讲述Condition Queue队列,这个指针不多介绍) |
next | 后继指针 |
线程两种锁的模式:
模式 | 含义 |
---|---|
SHARED | 表示线程以共享的模式等待锁 |
EXCLUSIVE | 表示线程正在以独占的方式等待锁 |
waitStatus有下面几个枚举值:
枚举 | 含义 |
---|---|
0 | 当一个Node被初始化的时候的默认值 |
CANCELLED | 为1,表示线程获取锁的请求已经取消了 |
CONDITION | 为-2,表示节点在等待队列中,节点线程等待唤醒 |
PROPAGATE | 为-3,当前线程处在SHARED情况下,该字段才会使用 |
SIGNAL | 为-1,表示线程已经准备好了,就等资源释放了(处于等待队列中第二位置 ) |