ReentrantLock Fair 与 Unfair 的巨大差异
ReentrantLock ?可重入的锁是我们平常除了intrinsic ?lock ?(也就是 synchronized 方法, synchronized block)之外用得最多的了同步方式了。 一般情况下 我们用?ReentrantLock ?的时候就是用它的默认建构函数方式?
? ? ?new?ReentrantLock ?();
但其实它带一个 参数 是否 fair。如果是true ?也就是FairSync 所在有多个线程同时竞争这个锁得时候, 会考虑公平性尽可能的让不同的线程公平。 这个公平其实是有很大的性能损失换来的。下面有个例子 : ?\
?
package com.bwang.concurrent;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;import java.util.concurrent.CyclicBarrier;import static java.lang.System.out;public final class TestLocks implements Runnable{ public enum LockType { JVM, JUC } public static LockType lockType; public static final long ITERATIONS = 5L * 1000L * 1000L; public static long counter = 0L; public static final Object jvmLock = new Object(); public static final Lock jucLock = new ReentrantLock(false); private static int numThreads; private final long iterationLimit; private final CyclicBarrier barrier; private long localCounter = 0L; public long getLocalCounter() { return localCounter; } public TestLocks(final CyclicBarrier barrier, final long iterationLimit) { this.barrier = barrier; this.iterationLimit = iterationLimit; } public static void main(final String[] args) throws Exception { lockType = LockType.valueOf("JUC"); numThreads = Integer.parseInt("8"); final long start = System.nanoTime(); runTest(numThreads, ITERATIONS); final long duration = System.nanoTime() - start; out.printf("%d threads, duration %,d (ns)\n", numThreads, duration); out.printf("%,d ns/op\n", duration / ITERATIONS); out.printf("%,d ops/s\n", (ITERATIONS * 1000000000L) / duration); out.println("counter = " + counter); } private static void runTest(final int numThreads, final long iterationLimit) throws Exception { CyclicBarrier barrier = new CyclicBarrier(numThreads); Thread[] threads = new Thread[numThreads]; TestLocks[] testLocks = new TestLocks[numThreads]; for (int i = 0; i < threads.length; i++) { testLocks[i] = new TestLocks(barrier, iterationLimit); threads[i] = new Thread(testLocks[i]); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } for (int i = 0; i < threads.length; i++) { out.printf("%d thread, local counter = %,d\n", i, testLocks[i].getLocalCounter()); } } public void run() { try { barrier.await(); } catch (Exception e) { // don't care } switch (lockType) { case JVM: jvmLockInc(); break; case JUC: jucLockInc(); break; } } private void jvmLockInc() { while (true) { long count = 0; synchronized (jvmLock) { ++counter; count = counter; } localCounter++; if (count >= iterationLimit) { break; } } } private void jucLockInc() { while (true) { long count = 0L; jucLock.lock(); try { ++counter; count = counter; } finally { jucLock.unlock(); } localCounter++; if (count >= iterationLimit) { break; } } }}
?
?
我们简单用N个线程来同步一个counter ?5,000,000次。 如果是?new ReentrantLock(true) 也就是 FairSync 方式 :
?
?
0 thread, local counter = 624,822
1 thread, local counter = 625,135
2 thread, local counter = 624,936
3 thread, local counter = 624,800
4 thread, local counter = 625,007
5 thread, local counter = 624,921
6 thread, local counter = 625,298
7 thread, local counter = 625,088
8 threads, duration 16,553,236,994 (ns)
3,310 ns/op
302,055 ops/s
counter = 5000007
?
?
可以看到8 个线程 每个线程的获取lock都很接近 但是它要 3310 个ns 来进行一次。 ? 如果采用??如果是?new ReentrantLock(false) 就是 UnfairSync 方式:
0 thread, local counter = 626,786
1 thread, local counter = 594,983
2 thread, local counter = 590,274
3 thread, local counter = 688,725
4 thread, local counter = 588,090
5 thread, local counter = 586,885
6 thread, local counter = 732,210
7 thread, local counter = 592,054
8 threads, duration 425,844,254 (ns)
85 ns/op
11,741,381 ops/s
counter = 5000007
虽然 每个thread 获取lock 的次数差异很大 从 ? 592,054到 ?732,210, ?但每次操作自需要 85 ns。 ?3310 对 85 这个差异太大聊。 ?
如果我们用intrinsic lock 的方法 结果如下:
?
0 thread, local counter = 498,363
1 thread, local counter = 512,603
2 thread, local counter = 799,367
3 thread, local counter = 500,946
4 thread, local counter = 824,935
5 thread, local counter = 652,921
6 thread, local counter = 692,219
7 thread, local counter = 518,653
8 threads, duration 877,777,848 (ns)
175 ns/op
5,696,202 ops/s
counter = 5000007
?
intrinsic lock 也应该是unfair 的方式, 每个线程获取的机会差异比较大, 每个操作需要 175ns。 ?比 unfair 的?ReentrantLock ?性能差些。 ??
?
得出的结果是 如果我们仅考虑同步锁得性能不需要考虑公平性优先考虑?
? ? ??new ReentrantLock(false) ? ?
再次是?intrinsic lock
万不得已的必须要FairSync 的情况下才用?new ReentrantLock(true)。