Lock作为多线程编程中的一个基础,与Lock对应的关键字为synchronized,同时大家都知道lock与synchronized的一个明显区别是Lock中的锁可以是公平锁,synchronized只能为非公平锁。本文将通过分析jdk的源码来了解ReentrantLock中公平锁和非公平锁的实现原理,这本是一个老生常谈的话题,因为想了解其过程的话比较简单的,网上一搜出来也是一大把,但网上的其它文章对于公平锁和非公平锁的区别很少有看到有实际实例,基于这个点我便决定自己来写个实例测试下公平/非公平锁的不同点,通过实例来了解与再次学习下jdk1.8中ReentrantLock关于非公平锁和公平锁的实现过程是怎么样的。
先来看一个Lock中公平锁与非公平锁的代码:
ReentrantLock非公平锁示例代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class NonFairLockTestMain {
public static void main(String[] args) {
Lock nonFairLock = new ReentrantLock(false);
Thread firstThread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().toString() + " pre run...");
nonFairLock.lock();
Thread.sleep(1_000);
System.out.println(Thread.currentThread().toString() + " run!!!!!!!!!!!!!!!!!!");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().toString() + " pre unlock@@@");
nonFairLock.unlock();
}
}
}, "firstThread");
//第一个线程先获取到锁
firstThread.start();
List<Thread> threadList = new ArrayList<>();
//定义101个线线,标号都为2000到2100之间
for (int i = 2000; i < 2101; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().toString() + " pre run...");
nonFairLock.lock();
System.out.println(Thread.currentThread().toString() + " run!!!");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
nonFairLock.unlock();
}
}
}, "thread_" + i);
threadList.add(thread);
}
//让N个线程去排队获取锁,标号都为100以下
for (int i = 0; i < 101; i++) {
int finalI = i;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().toString() + " pre run...");
nonFairLock.lock();
System.out.println(Thread.currentThread().toString() + " run!!!");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
nonFairLock.unlock();
if (finalI == 0) {
threadList.forEach(thread1 -> {
thread1.start();
});
}
}
}
}, "thread_" + i);
thread.start();
}
}
}
运行几次之后,观察输出结果,发现会出现如下这种情况:
其输出片段如下:
Thread[thread_98,5,main] pre run...
Thread[thread_99,5,main] pre run...
Thread[thread_100,5,main] pre run...
Thread[firstThread,5,main] run!!!!!!!!!!!!!!!!!!
Thread[firstThread,5,main] pre unlock@@@
Thread[thread_1,5,main] run!!!
Thread[thread_0,5,main] run!!!
Thread[thread_2,5,main] run!!!
Thread[thread_3,5,main] run!!!
Thread[thread_4,5,main] run!!!
Thread[thread_2000,5,main] pre run...
Thread[thread_2003,5,main] pre run...
Thread[thread_2002,5,main] pre run...
Thread[thread_2001,5,main] pre run...
Thread[thread_2004,5,main] pre run...
Thread[thread_2006,5,main] pre run...
Thread[thread_2010,5,main] pre run...
Thread[thread_2007,5,main] pre run...
Thread[thread_2008,5,main] pre run...
Thread[thread_2009,5,main] pre run...
Thread[thread_2005,5,main] pre run...
Thread[thread_2011,5,main] pre run...
Thread[thread_2012,5,main] pre run...
Thread[thread_2013,5,main] pre run...
Thread[thread_2014,5,main] pre run...
Thread[thread_2015,5,main] pre run...
Thread[thread_2016,5,main] pre run...
Thread[thread_2017,5,main] pre run...
Thread[thread_2018,5,main] pre run...
Thread[thread_2019,5,main] pre run...
Thread[thread_2020,5,main] pre run...
Thread[thread_2021,5,main] pre run...
Thread[thread_2022,5,main] pre run...
Thread[thread_2023,5,main] pre run...
Thread[thread_2024,5,main] pre run...
Thread[thread_2025,5,main] pre run...
Thread[thread_2026,5,main] pre run...
Thread[thread_2027,5,main] pre run...
Thread[thread_2027,5,main] run!!!
Thread[thread_2028,5,main] pre run...
批号为0~100的线程还未运行完毕时,编号为2027的这个线程就先运行了!
这是啥?编号为2027号的线程插队了!
非公平锁原理分析
进入lock的lock()方法查看:
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}
在非公平锁中,其sync.lock方法的实现代码为:
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
NonfairSync锁当前空闲状态时处理办法
从代码里可以看出,lock方法执行的时候会先用cas来判断当前锁是否有线程在占用,如果cas成功,也就是成功将1设到state上去了的话,那么当时锁是没有线程在占用的,那么最后会执行将当前线程设到AbstractOwnableSynchronizer中
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
AbstractOwnableSynchronizer是个啥?看下非公平锁NonfairSync的继承关系:
Nonfair间接继承于AbstractOwnableSynchronizer,也就是把当前线程设为此锁的拥有者
NonfairSync锁当前处于非空闲状态时处理办法
还是看上方的代码,当cas(0,1)失败后代表当前锁里目前有线程在使用了,那么会执行acquire(1),其源码为:
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire方法会执行如下几个判断:
- !tryAcquire(1)
- acquireQueued(addWaiter(Node.EXCLUSIVE),1))
tryAcquire方法
当前环境下的tryAcquire(1)代码为:
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
也就是再次判断state是否真的没有别的线程在占用(返回true),以及是否当前线程早就已经占用了此锁了(返回true),否则返回false
然后再了解下acquireQueued(addWaiter(Node.EXCLUSIVE),1))
addWaiter方法
先看addWaiter(Node.EXCLUSIVE)方法(java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter):
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
上面的代码也就是排队,排在AbstractQueuedSynchronizer里,也就是人人常说的AQS里。
enq(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
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq方法采用了自旋非阻塞的方式进行了入队操作
acquireQueued方法
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
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);
}
}
此方法的for循环中主要有两个if判断,先看一下第二个if判断,其中有两个方法
分别是shouldParkAfterFailedAcquire(p, node)和parkAndCheckInterrupt()
再具体看下
shouldParkAfterFailedAcquire(p, node)方法
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驱节点的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// CANCELLED = 1;
// SIGNAL = -1;
// CONDITION = -2;
// PROPAGATE = -3;
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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
此方法为判断此当前线程能否可以挂起的方法,从代码可以看出,线程能否被挂起的条件是:
当前线程节点的前置节点的waitStatus状态为SIGNAL
parkAndCheckInterrupt方法
- !tryAcquire(1) //再次确定当前锁非空闲
- acquireQueued(addWaiter(Node.EXCLUSIVE),1))
如果上面的这两个判断都ok的话,最终会执行一个selfInterrupt(),也就是自己中断自己
ReentrantLock公平锁示例代码
写着写着就感觉有点写偏题了,说好的只来记录下公平锁和非公平锁的实现部分的,结果还记录了一些关于ReentrantLock相关的代码,尴尬了。。。
为了节约文章篇幅,这里就不再将公平锁演示的代码贴出来了,代码与非公平锁的示例代码一样,只需要将在创建锁时将锁new为ReentrantLock(true)就可以了,即:new ReentrantLock(true)
然可尝试运行几次,观察输出结果,从结果中会发现就不会再出现插队的情况了
公平锁原理分析
公平锁中调用锁时调用的是FairSync的lock方法,其lock方法会通过acquire(1)方法调用FairSync下的tryAcquire方法
java.util.concurrent.locks.ReentrantLock.FairSync
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
再来回顾对比下非公平锁的lock方法:
java.util.concurrent.locks.ReentrantLock.NonfairSync
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
总结
通过观察可以发现:公平锁的lock方法在进行cas判断时多了一个hasQueuedPredecessors()方法,它会在AQS队列中没有中没有线程的情况下才会申请锁,而不像非公平锁一样,非公平锁一来不管AQS里是否有排队的线程就直接申请锁。
最近尝到了不受公司领导重视的滋味,在公司没有存在感,开会不叫我,分配任务不分配给我
看着身边的同事都有开发任务时,我只能羡慕
从事程序员以来也是第一次感受到这种情况,让我一次次怀疑自己
加油吧!小老弟!