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)。