ReentrantLock Fair 与 Unfair 的巨大差异

ReentrantLock  可重入的锁是我们平常除了intrinsic  lock  (也就是 synchronized 方法, synchronized block)之外用得最多的了同步方式了。 一般情况下 我们用 ReentrantLock  的时候就是用它的默认建构函数方式 

     new ReentrantLock  ();

但其实它带一个 参数 是否 fair。如果是true  也就是FairSync 所在有多个线程同时竞争这个锁得时候, 会考虑公平性尽可能的让不同的线程公平。 这个公平其实是有很大的性能损失换来的。下面有个例子 :  \

 

Testlocks代码   收藏代码
  1. package com.bwang.concurrent;  
  2.   
  3. import java.util.concurrent.locks.Lock;  
  4. import java.util.concurrent.locks.ReentrantLock;  
  5. import java.util.concurrent.CyclicBarrier;  
  6.   
  7. import static java.lang.System.out;  
  8.   
  9. public final class TestLocks implements Runnable  
  10. {  
  11.     public enum LockType { JVM, JUC }  
  12.     public static LockType lockType;  
  13.   
  14.     public static final long ITERATIONS = 5L * 1000L * 1000L;  
  15.     public static long counter = 0L;  
  16.   
  17.     public static final Object jvmLock = new Object();  
  18.     public static final Lock jucLock = new ReentrantLock(false);  
  19.     private static int numThreads;  
  20.   
  21.     private final long iterationLimit;  
  22.     private final CyclicBarrier barrier;  
  23.     private long localCounter = 0L;  
  24.     public long getLocalCounter()   
  25.     {  
  26.         return localCounter;  
  27.     }   
  28.   
  29.     public TestLocks(final CyclicBarrier barrier, final long iterationLimit)  
  30.     {  
  31.         this.barrier = barrier;  
  32.         this.iterationLimit = iterationLimit;  
  33.     }  
  34.   
  35.     public static void main(final String[] args) throws Exception  
  36.     {  
  37.         lockType = LockType.valueOf("JUC");  
  38.         numThreads = Integer.parseInt("8");  
  39.   
  40.         final long start = System.nanoTime();  
  41.         runTest(numThreads, ITERATIONS);  
  42.         final long duration = System.nanoTime() - start;  
  43.   
  44.         out.printf("%d threads, duration %,d (ns)\n", numThreads, duration);  
  45.         out.printf("%,d ns/op\n", duration / ITERATIONS);  
  46.         out.printf("%,d ops/s\n", (ITERATIONS * 1000000000L) / duration);  
  47.         out.println("counter = " + counter);  
  48.     }  
  49.   
  50.     private static void runTest(final int numThreads, final long iterationLimit)  
  51.         throws Exception  
  52.     {  
  53.         CyclicBarrier barrier = new CyclicBarrier(numThreads);  
  54.         Thread[] threads = new Thread[numThreads];  
  55.         TestLocks[] testLocks = new TestLocks[numThreads];  
  56.         for (int i = 0; i < threads.length; i++)  
  57.         {  
  58.             testLocks[i] = new TestLocks(barrier, iterationLimit);  
  59.             threads[i] = new Thread(testLocks[i]);  
  60.         }  
  61.   
  62.         for (Thread t : threads)  
  63.         {  
  64.             t.start();  
  65.         }  
  66.   
  67.         for (Thread t : threads)  
  68.         {  
  69.             t.join();  
  70.         }  
  71.         for (int i = 0; i < threads.length; i++)  
  72.         {  
  73.             out.printf("%d thread, local counter = %,d\n", i, testLocks[i].getLocalCounter());  
  74.         }  
  75.     }  
  76.   
  77.     public void run()  
  78.     {  
  79.         try  
  80.         {  
  81.             barrier.await();  
  82.         }  
  83.         catch (Exception e)  
  84.         {  
  85.             // don't care  
  86.         }  
  87.   
  88.         switch (lockType)  
  89.         {  
  90.             case JVM: jvmLockInc(); break;  
  91.             case JUC: jucLockInc(); break;  
  92.         }  
  93.     }  
  94.   
  95.     private void jvmLockInc()  
  96.     {  
  97.           
  98.         while (true)  
  99.         {  
  100.             long count = 0;  
  101.             synchronized (jvmLock)  
  102.             {  
  103.                 ++counter;  
  104.                 count = counter;  
  105.             }  
  106.             localCounter++;  
  107.             if (count >= iterationLimit)  {  
  108.                 break;  
  109.             }  
  110.         }  
  111.     }  
  112.   
  113.     private void jucLockInc()  
  114.     {  
  115.         while (true)  
  116.         {  
  117.             long count = 0L;  
  118.             jucLock.lock();  
  119.             try  
  120.             {  
  121.                 ++counter;  
  122.                 count = counter;  
  123.             }  
  124.             finally  
  125.             {  
  126.                 jucLock.unlock();  
  127.             }  
  128.             localCounter++;  
  129.             if (count >= iterationLimit)  {  
  130.                 break;  
  131.             }  
  132.         }  
  133.     }  
  134. }  

 

 

我们简单用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)。

你可能感兴趣的:(juc)