在Java实现管程的方式已经有了synchronized,而synchronized进行锁升级时带来的开销也是不一个不容忽略的问题。因为Java实现了一个相对轻量级的锁ReentrantLock。但是加锁和解锁具体的如何实现的?带着疑问我们来剖析下并发工具包中Lock锁的实现原理。
下面以ReentrantLock为例分析下实现原理:
ReentrantLock核心部分是使用了AbstractQueuedSynchronizer框架(以下简称AQS框架),而AQS框架也是Java并发编程包里的基础框里。
-
如何保证加锁和解锁的状态
简述锁的状态机制:通过一个volatile修饰的int变量state来表示锁数量,0代表目前无线程获得锁,大于0表示线程持有锁的数据。通过CAS操作修改state的值保证原子性,根据volatile的内存语义来显示变量修改的可见性,从而是所有线程读到的state的值都是最新的。
- 可重入锁机制
可重入锁的机制的实现原理也是volatile修饰的int变量state来实现的,当前线程持有锁时,state会在原来的基础上加1,来表示持有锁的数量。(注:必须是同一线程重复获取锁)。
- 队列同步器CHL机制
在AbstractQueuedSynchronizer中实现了Node的内部类,也是这是队列同步器CHL的主要实现元素。Node主要为了实现条件队列(用于独占模式,由单向的链表实现),等待队列(用于独占模式来共享模式,由双向链表实现),理解这两种队列的实现方式,对于阅读源码有很大帮助。
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
//用于共享模式实现节点(readLock)
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;
//节点状态
volatile int waitStatus;
//等待队列的前继节点
volatile Node prev;
//等待队列的后继节点
volatile Node next;
//节点中的线程
volatile Thread thread;
//在等待队列中,用来判断是独占模式还是共享模式
//在条件队列中节点的后继节点
Node nextWaiter;
//判断是不是共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
//获取节点的前驱节点
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;
}
}
- 可重入锁的实现机制: 2.第一次cas操作,也是第一加锁的动作,如果成功,表示该锁是第一次加锁,如果失败,可能存在两种情况,(1) 两个线程在竞争同一把锁,(2)锁早已经别持有,也许是自己也可能是其他线程。 3.首次加锁失败,在读state的值,这里又会有三种情况,state是0锁已经放开了,会尝试再次加锁;锁没有放开但是确实当前线程持有的锁,直接修改锁持有的数据,这也是可从重入锁的实现机制,锁既没有释放,也不是当前线程持有的锁,那就直接返回false加锁失败。
在ReentrantLock源码中,支持公共锁和非公平锁:
默认是创建非公平锁,根据参数可创建公平锁。
//支持创建公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//创建非公共平锁
public ReentrantLock() {
sync = new NonfairSync();
}
NonfairSync(非公平锁)和FaifSync(公平锁)是ReentrantLock的内部类,是锁机制的主要实现,两个内部类都是继承内部类Sync,而Sync继承了AbstractQueuedSynchronizer。所以ReenTrantLock最终锁的实现机制还是通过AQS框架实现。下面就来看看加锁和解锁的操作:
//加锁
lock();
//解锁
unlock();
以非公平锁为例分析源码:
- 加锁操作:
- 非公平锁简单暴力直接尝试原子操作CAS获取锁(修改state值为1),加锁成功直接结束。加锁失败的原因主要有两种:1(1) 两个线程在竞争同一把锁,(2)锁早已经别持有,也许是自己也可能是其他线程,加锁不成功执行 2、方法acquire()
- 加锁失败后的处理:如果是因为锁已经被获取进入了加锁失败的处理逻辑,但是此时可能会出现一种情况就是已经持有所的线程释放了锁,所以为了避免无用的浪费时间,需要重新确认是否能获取到锁。即调用Sync的tryAcquire(int acquires)方法执行 2-1.nonfairTryAcquire(int acquires),如果nonfairTryAcquire(int acquires)返回true表示加锁成功,否则调用2-2acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法将线程加入阻塞队列中;
- 2-1.nonfairTryAcquire(int acquires)方法中获取当前线程和state值,如果state的值为0,表示没有现在获得锁,将当前现在置为占用线程,返回true;当state不为0时,表示此时有线程已经得到锁,如果此时线程为占用线程,表示此线程已获得锁,则继续修改state的值继续+1,返回true。这也就是可重入锁的实现机制。否则返回false。
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
//1.加锁方法
final void lock() {
通过原子操作获取锁(修改state值为1)
if (compareAndSetState(0, 1))
@2将当前线程设置为占用线程
setExclusiveOwnerThread(Thread.currentThread());
else
2.加锁失败后的处理:加锁失败后尝试获取锁,如果仍然获取不到加入到等待队列中
acquire(1);
}
//2.加锁失败后的处理:加锁失败后尝试获取锁,如果仍然获取不到加入到等待队列中
public final void acquire(int arg) {
//尝试再次加锁(调用子类方法)
if (!tryAcquire(arg) &&//2-1
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//3-1
selfInterrupt();
}
//2-1尝试加锁
protected final boolean tryAcquire(int acquires) {
//非公平锁尝试加锁
return nonfairTryAcquire(acquires);
}
//2-2非公平锁尝试加锁
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取state值
int c = getState();
if (c == 0) {
//如果状态值为0,通过原子CAS操作去修改状态值,返回
if (compareAndSetState(0, acquires)) {
设置占用线程
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前线程是否占用线程(可重入锁实现机制)
else if (current == getExclusiveOwnerThread()) {
//状态state累加
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//回写state
setState(nextc);
return true;
}
return false;
}
- 3-1如果2-1返回的是true表示加锁成功;返回false则继续执行加入等待队列的操作。在acquireQueued()之前会先执行3-2addWaiter(Node.EXCLUSIVE), arg)加入等待队列成功,返回当前尾节点。继续执行3-1方法,进入循环,获取当前尾节点的前驱节点,如果前驱节点为头节点,继续尝试加锁,加锁成功直接,将尾节点清空,返回。加锁失败,执行3-3方法shouldParkAfterFailedAcquire。
- 3-2首先将线程加入到节点Node中,判断当前对列中尾节点是不为空的情况,通过原子操作CAS将节点Node追加到队列的尾部,修改指针;如果节点为空,则调用enq方法。通过自旋,首先再次判断尾节点为空,通过原子性操作CAS设置一个空的头结点(哨兵的作用),继续循环,如果尾节点不为空,通过原子性操作CAS将当前线程追加到尾节点,返回尾当前尾节点。继续执行3-1
- 3-3进入shouldParkAfterFailedAcquire方法,首先获取前驱节点的信号,信号为Node.SIGNAL,表示现在目前处于阻塞中,直接返回。信号>0,表示前驱节点线程以中断或超时,舍弃前驱节点,直到获取有效节点重新规划队列节点。其他状态通过原子操作CAS设置前驱节点状态为Node.SIGNAL阻塞。最后执行parkAndCheckInterrupt方法调用 LockSupport.park方法阻塞线程,返回线程中断位,结束。
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
//3-2加入到等待队列中,此时mode为Node.EXCLUSIVE表示是独占模式
private Node addWaiter(Node mode) {
//设置含有当前线程的节点,
Node pred = tail;
//判断尾节点不为空,表示当前已经有节点在同步队列中不为空,调整队列结构
if (pred != null) {
//将当前节点追加到尾节点后
node.prev = pred;
//通过原子CAS操作,将当前节点追加到尾节点后
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//尾节点不为空的操作,说明当前等待队列中是空的
enq(node);
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
//没有尾节点情况
private Node enq(final Node node) {
//通过自旋
for (;;) {
//判断尾节点为空
Node t = tail;
if (t == null) { // Must initialize
//通过原子性操作CAS设置一个空的头结点(哨兵的作用)
if (compareAndSetHead(new Node()))
tail = head;
} else {
//如果尾节点不为空,通过原子性操作CAS将当前线程追加到尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
//返回当前线程节点
return t;
}
}
}
}
//3-1此时占有线程可能已经执行结束,所以此时选择继续尝试加锁,加锁成功,继续进入循环,加锁失败阻塞线程
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)) {
//设置首节点,请除线程引用,清除原来的head引用(首节点表示当前获取锁的节点)
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//3-3加锁失败,修改等待节点状态,修改成功,阻塞线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//阻塞队列
interrupted = true;
}
} finally {
if (failed)
//线程超时或异常会执行,主要清除中断或超时的等待队列的线程节点
cancelAcquire(node);
}
}
//3-3修改节点状态,修改成功阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//获取前驱节点的状态(信号)
if (ws == Node.SIGNAL)//信号为-1,表示现在目前处于阻塞中,直接返回
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {//信号>0,表示前驱节点线程以中断或超时,舍弃前驱节点,知道获取有效节点重新规划队列节点
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//ws默认值是0无状态或者PROPAGATE,原子操作CAS设置前驱节点状态为Node.SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//调用 LockSupport.park方法阻塞线程,返回线程中断位
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 解锁:
- 调用2)sync.release(1);
- 进入release方法首先执行tryRelease修改锁状态,锁状态state为0时,置空占用线程(重入锁修改状态直到为0),回写state状态。如果修改锁的状态成功则开始唤醒阻塞线程3)unparkSuccessor(Node node)
- 首先获取头节点,判断头节点的信号,如果头节点的ws<0则获取头节点后继节点,后继节点不为空,且信号为中断或者超时状态时,循环遍历后继节点,指定找到有效后继节点,最后//唤醒线程LockSupport.unpark(s.thread);
//1.解锁
public void unlock() {
sync.release(1);
}
//2.修改锁状态
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();
//锁状态state为0时,置空占用线程(重入锁修改状态直达为0)
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
//3)叫醒阻塞线程
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)
//原子操作CAS设置首节点信号为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);
}
思考公平锁和非公平锁的区别:
非公平锁首先是尝试加锁,如果加锁成功即返回,而公平锁,先是去判断锁是否被持有,再判断是否队里中已经有线程等待,如果没有才尝试修改锁的状态。