Sync 有两个实现,分别为 NonfairSync(非公平锁)和 FairSync(公平锁),我们看 FairSync 部分。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
FairSync的使用,通过构造方法创建,然后通过 local方法获取锁和unLock方法释放锁。
public class FairLock implements Runnable {
public static ReentrantLock fairLock = new ReentrantLock(true);
public static void main(String[] args) {
FairLock fairLock = new FairLock();
Thread t1 = new Thread(fairLock, "Thread_t1");
Thread t2 = new Thread(fairLock, "Thread_t2");
Thread t3 = new Thread(fairLock, "Thread_t3");
t1.start();
t2.start();
t3.start();
}
@Override
public void run() {
while (true) {
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁");
} finally {
fairLock.unlock();
}
}
}
}
AQS的结构
先看看AQS的属性
// 当前持有锁的线程
private transient volatile Node head;
//阻塞队列的尾节点
private transient volatile Node tail;
//当前锁的状态 0表示没有被占用 大于0表示线程持有当前锁
private volatile int state;
//代表持有独占锁的线程
private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
AQS的等待序列如下图所示 线程没有抢到锁就会进入阻塞队列阻塞队列不包含head
进入阻塞队列的线程会被包装成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;
// ======== 下面的几个int常量是给waitStatus用的 ===========
//表示节点取消了争抢这个锁
static final int CANCELLED = 1;
//表示当前节点的后续节点对应的线程要被唤醒
static final int SIGNAL = -1;
//conditon下使用的状态
static final int CONDITION = -2;
//占时用不上
static final int PROPAGATE = -3;
//线程的状态
volatile int waitStatus;
//前节点
volatile Node prev;
//后节点
volatile Node next;
//线程本身
volatile Thread thread;
//实现条件队列的单向链表
Node nextWaiter;
}
线程抢锁lock()这个方法做了什么
1.尝试获取锁 (条件:state为0和阻塞队列没有线程等待)
根据前面说的state状态,0表示可以获取锁,大于0表示锁被线程占用 ,如果是0 代表可以抢锁,由于是公平锁 ,所有要看阻塞队列没有线程在等待。 如果没有那么就进行cas抢锁 抢到锁state+1 返回true 否则就是这一时刻被其他线程抢到锁返回false 。
2.进入阻塞队列(条件:前面没有获取到锁)
没抢到锁就会进入阻塞队列 先把自己分装成node 然后挂到AQS的尾节点后面 由于大家都可以挂所有这里也采用cas挂节点
3线程是否挂起(执行2之后执行)
不断自旋后面的过程直至返回线程获取所或者挂起
判断自己是不是阻塞队列的第一个如果是第一个 则执行1尝试获取锁,如果获取锁则自旋结束,否则判断该线程是否应该挂起,首先看看前节点的waitStatus的状态是否为SIGNAL(-1)如果为SIGNAL那么执行4挂起。不是则判断waitStatus是否取消了排队 状态是则往前找节点找到不是取消状态的,然后把自己的节点挂在那个节点上好让其他线程唤醒 ,如果不是取消状态 则CAS将前驱节点的waitStatus设置为Node.SIGNAL(也就是-1)
4.挂起 LockSupport.park(this)返回Thread.interrupted();
下面看看lock的源码
步骤1:尝试获取锁
public void lock() {
sync.lock();
}
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
//步骤1尝试获取锁
if (!tryAcquire(arg) &&
//步骤3(步骤2)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//步骤1尝试获取锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// state == 0 此时此刻没有线程持有锁
int c = getState();
if (c == 0) {
// 虽然此时此刻锁是可以用的,但是这是公平锁,既然是公平,就得讲究先来后到,
// 看看有没有别人在队列中等了半天了
if (!hasQueuedPredecessors() &&
// 如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了,
// 不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了
// 因为刚刚还没人的,前面才刚判断
compareAndSetState(0, acquires)) {
//标记一下,告诉大家,现在是我占用了锁
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//状态加1因为锁是可重入的
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
步骤2:进入阻塞队列
private Node addWaiter(Node mode) {
//把线程包装成node
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;
//cas把节点挂入尾节点后面
if (compareAndSetTail(pred, node)) {
// 进到这里说明设置成功,当前node==tail, 将自己与之前的队尾相连,
// 上面已经有 node.prev = pred
// 加上下面这句,也就实现了和之前的尾节点双向连接了
pred.next = node;
return node;
}
}
// 采用自旋的方式入队
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 初始化head节点
// 初始化node的时候head和tail都是null
if (t == null) { // Must initialize
// 还是CAS,现在可能是很多线程同时进来呢
if (compareAndSetHead(new Node()))
tail = head;
} else {
//和前面一样的挂到尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
步骤3:线程是否挂起
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取前节点
final Node p = node.predecessor();
//p==head 说明当前节点是阻塞队列的第一个可以尝试获取锁
//为什么是尝试head可能是刚初始化的head不属于任何线程所以尝试获取锁
if (p == head && tryAcquire(arg)) {
//设置head表示这个node持有当前的锁
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//是否应该阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
//步骤4
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驱节点的线程状态
int ws = pred.waitStatus;
//表示这个节点的后续节点对应的线程要被唤醒
if (ws == Node.SIGNAL)
//直接返回true 要被阻塞 等待其他线程唤醒
return true;
//前驱节点 waitStatus大于0,说明前驱节点取消了排队。
// 进入阻塞队列排队的线程会被挂起,而唤醒的操作是由前驱节点完成的。
//找前驱节点的前驱节点知道找到节点状态waitStatus<=0的节点
//并把当前节点挂在找到的后续节点上
if (ws > 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.
*/
//前面都没有看到设置waitStatus 每个新的node进入队时都是0
//cas设置为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//步骤4 阻塞并返回是否中断的状态
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
解锁操作 unlock()做的什么
尝试释放锁 如果释放成功则唤醒后续节点
unlock()源码
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;
//为0说明没有嵌套锁了,可以释放了,否则还不能释放掉
if (c == 0) {
//释放成功的状态
free = true;
//把独占线程的标志设置为空
setExclusiveOwnerThread(null);
}
//修改独占状态
setState(c);
return free;
}
//唤醒后继节点
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;
// 如果head节点当前waitStatus<0, 将其修改为0
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;
//由后往前找符合条件的唤醒节点
//为什么要由后往前找 addWaiter(Node mode)方法中先cas设置tail成功之后在设置head的
//这里存在并发 如果head还没来得急设置的话 用head就无法遍历到
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//唤醒线程
LockSupport.unpark(s.thread);
}
FairSync 非公平锁
lock源码
final void lock() {
//直接先抢锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
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)) {
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;
}
unlock和公平锁的释放一样 只做释放锁的操作 因为阻塞队列为空不会执行唤醒节点的操作