JMH测试
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(iterations = 10)
@Warmup(iterations = 5)
@State(Scope.Group)
public class FairAndUnfairTest {
@Param({"1", "2"})
private int type;
private static Lock lock;
@Setup
public void setup() {
switch (type) {
case 1:
lock = new ReentrantLock(true);
break;
case 2:
//默认就是非公平锁
lock = new ReentrantLock(false);
break;
default:
throw new IllegalArgumentException("illegal lock type.");
}
}
@Benchmark
@GroupThreads(5)
@Group("lock")
public void put() {
lock.lock();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private int randomIntValue() {
return (int) Math.ceil(Math.random() * 600000);
}
@Benchmark
@GroupThreads(5)
@Group("lock")
public Integer get() {
lock.lock();
try {
Thread.sleep(10);
return randomIntValue();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return 0;
}
public static void main(String[] args) throws RunnerException {
Options opts = new OptionsBuilder()
.include(FairAndUnfairTest.class.getSimpleName())
.forks(1)
.build();
new Runner(opts).run();
}
}
读写分别五个线程,可以看出结果非公平锁性能高于公平锁
Benchmark (type) Mode Cnt Score Error Units
FairAndUnfairTest.lock 1 avgt 10 113303.572 ± 826.288 us/op
FairAndUnfairTest.lock:get 1 avgt 10 113294.981 ± 843.621 us/op
FairAndUnfairTest.lock:put 1 avgt 10 113312.163 ± 813.306 us/op
FairAndUnfairTest.lock 2 avgt 10 1668873.612 ± 3378209.364 us/op
FairAndUnfairTest.lock:get 2 avgt 10 1981826.499 ± 4488348.226 us/op
FairAndUnfairTest.lock:put 2 avgt 10 1355920.726 ± 2861627.232 us/op
代码分析
ReentrantLock 默认采用非公平锁,除非在构造方法中传入参数 true 。
//默认
public ReentrantLock() {
sync = new NonfairSync();
}
//传入true or false
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁的 lock 方法:
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
// AbstractQueuedSynchronizer.acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待
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;
}
}
我们可以看到,在注释1的位置,有个!hasQueuedPredecessors()条件,意思是说当前同步队列没有前驱节点(也就是没有线程在等待)时才会去compareAndSetState(0, acquires)使用CAS修改同步状态变量。所以就实现了公平锁,根据线程发出请求的顺序获取锁。
非公平锁的lock方法
static final class NonfairSync extends Sync {
final void lock() {
// 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// AbstractQueuedSynchronizer.acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 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) {
//3.这里也是直接CAS,没有判断前面是否还有节点。
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;
}
非公平锁的实现在刚进入lock方法时会直接使用一次CAS去尝试获取锁,不成功才会到acquire方法中,如注释2。而在nonfairTryAcquire方法中并没有判断是否有前驱节点在等待,直接CAS尝试获取锁,如注释3。由此实现了非公平锁。
总结
非公平锁和公平锁的两处不同:
-
非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
-
非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。
公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。