硬體環境: CPU:AMD Phenom(tm) II X4 955 Processor Memory:8G SSD(128G):/ HDD(1T):/home/ 軟體環境: OS:Ubuntu14.04.3 LTS Java:JDK1.7 關於ReentrantLock中非公平鎖和公平鎖詳細區別以 ...
硬體環境:
CPU:AMD Phenom(tm) II X4 955 Processor
Memory:8G
SSD(128G):/
HDD(1T):/home/
軟體環境:
OS:Ubuntu14.04.3 LTS
Java:JDK1.7
關於ReentrantLock中非公平鎖和公平鎖詳細區別以及實現方式在這裡不再敘述,有關ReentrantLock的源碼解析參照。
首先我們用實例驗證,非公平鎖以及公平鎖是否是其介紹的那樣,非公平鎖在獲取鎖的時候會首先進行搶鎖,在獲取鎖失敗後才會將當前線程加入同步隊列隊尾中,而公平鎖則是符合請求的絕對順序,也就是會按照先來後到FIFO。
1 package com.lock; 2 3 import org.junit.Test; 4 5 import java.util.ArrayList; 6 import java.util.Collection; 7 import java.util.Collections; 8 import java.util.List; 9 import java.util.concurrent.locks.Lock; 10 import java.util.concurrent.locks.ReentrantLock; 11 12 /** 13 * Created by yulinfeng on 5/24/17. 14 */ 15 public class FairAndUnfairTest { 16 private static Lock fairLock = new ReentrantLockMine(true); 17 private static Lock unfairLock = new ReentrantLockMine(false); 18 19 @Test 20 public void unfair() throws InterruptedException { 21 testLock("非公平鎖", unfairLock); 22 } 23 24 @Test 25 public void fair() throws InterruptedException { 26 testLock("公平鎖", fairLock); 27 } 28 29 private void testLock(String type, Lock lock) throws InterruptedException { 30 System.out.println(type); 31 for (int i = 0; i < 5; i++) { 32 Thread thread = new Thread(new Job(lock)){ 33 public String toString() { 34 return getName(); 35 } 36 }; 37 thread.setName("" + i); 38 thread.start(); 39 } 40 Thread.sleep(11000); 41 } 42 43 private static class Job implements Runnable{ 44 private Lock lock; 45 public Job(Lock lock) { 46 this.lock = lock; 47 } 48 49 public void run() { 50 for (int i = 0; i < 2; i++) { 51 lock.lock(); 52 try { 53 Thread.sleep(1000); 54 System.out.println("獲取鎖的當前線程[" + Thread.currentThread().getName() + "], 同步隊列中的線程" + ((ReentrantLockMine)lock).getQueuedThreads() + ""); 55 } catch (InterruptedException e) { 56 e.printStackTrace(); 57 } finally { 58 lock.unlock(); 59 } 60 } 61 } 62 } 63 64 private static class ReentrantLockMine extends ReentrantLock { //重新實現ReentrantLock類是為了重寫getQueuedThreads方法,便於我們試驗的觀察 65 public ReentrantLockMine(boolean fair) { 66 super(fair); 67 } 68 69 @Override 70 protected Collection<Thread> getQueuedThreads() { //獲取同步隊列中的線程 71 List<Thread> arrayList = new ArrayList<Thread>(super.getQueuedThreads()); 72 Collections.reverse(arrayList); 73 return arrayList; 74 } 75 } 76 }
上面這段代碼:創建5個線程,每個線程中有兩次獲取鎖與釋放鎖的行為。運行代碼觀察結果:
顯然,試驗結果與我們的預期相符。在以非公平鎖的方式獲取鎖,當一個線程在獲取鎖又釋放鎖,但又立即獲取鎖的時候,這個時候這個線程有很大的概率會成功(只是很大概率,試驗結果也有可能不連續兩次獲取鎖)。而公平鎖則不一樣,哪怕是同一個線程連續兩次獲取鎖和釋放鎖,在第一次獲取鎖釋放鎖過後接著準備第二次獲取鎖時,這個時候當前線程會被加入到同步隊列的隊尾。
那麼有了上面的結果除了說明非公平鎖和公平鎖之間的區別還能說明什麼問題呢?其實,這就是本篇的主題——性能測試。非公平鎖的一個線程連續兩次獲取鎖和釋放鎖的工程中,是沒有做上下文切換的,也就是一共只做了5次上下文切換。而公平鎖實際上做了10次上下文切換。而這個上下文切換的開銷實際是很大的,我們通過測試在10個線程,每個線程獲取100000次鎖的情況下兩者的執行速度,以及使用vmstat命令來統計系統上下文切換的次數(cs欄表示系統每秒切換的上下文次數)。
1 package com.lock; 2 3 import java.util.ArrayList; 4 import java.util.Collection; 5 import java.util.Collections; 6 import java.util.List; 7 import java.util.concurrent.BrokenBarrierException; 8 import java.util.concurrent.CyclicBarrier; 9 import java.util.concurrent.locks.Lock; 10 import java.util.concurrent.locks.ReentrantLock; 11 12 /** 13 * 改進後的代碼,利用CyclicBarrier當所有線程執行完畢時,統計執行時間。 14 * Created by yulinfeng on 5/24/17. 15 */ 16 public class newFairAndUnfairLockTest { 17 private static Lock lock = new ReentrantLockMine(false); //非公平鎖 18 //private static Lock lock = new ReentrantLockMine(true); //公平鎖 19 20 public static void main(String[] args) throws BrokenBarrierException, InterruptedException { 21 String lockType = "非公平鎖"; //String lockType = "公平鎖" 22 long start = System.currentTimeMillis(); 23 CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Time(lockType, start)); //10個線程執行完畢時,執行Time線程統計執行時間 24 25 for (int i = 0; i < 10; i++) { 26 Thread thread = new Thread(new Job(lock, cyclicBarrier)){ 27 public String toString() { 28 return getName(); 29 } 30 }; 31 thread.setName("" + i); 32 thread.start(); 33 } 34 35 36 } 37 38 private static class Job implements Runnable{ 39 private Lock lock; 40 private CyclicBarrier cyclicBarrier; 41 public Job(Lock lock, CyclicBarrier cyclicBarrier) { 42 this.lock = lock; 43 this.cyclicBarrier = cyclicBarrier; 44 } 45 46 public void run() { 47 for (int i = 0; i < 100000; i++) { 48 lock.lock(); 49 try { 50 System.out.println(i+"獲取鎖的當前線程[" + Thread.currentThread().getName() + "], 同步隊列中的線程" + ((ReentrantLockMine)lock).getQueuedThreads() + ""); 51 } finally { 52 lock.unlock(); 53 } 54 } 55 try { 56 cyclicBarrier.await(); //計數器+1,直到10個線程都到達 57 } catch (InterruptedException e) { 58 e.printStackTrace(); 59 } catch (BrokenBarrierException e) { 60 e.printStackTrace(); 61 } 62 } 63 } 64 65 private static class ReentrantLockMine extends ReentrantLock { //重新實現ReentrantLock類是為了重寫getQueuedThreads方法,便於我們試驗的觀察 66 public ReentrantLockMine(boolean fair) { 67 super(fair); 68 } 69 70 @Override 71 protected Collection<Thread> getQueuedThreads() { //獲取同步隊列中的線程 72 List<Thread> arrayList = new ArrayList<Thread>(super.getQueuedThreads()); 73 Collections.reverse(arrayList); 74 return arrayList; 75 } 76 } 77 78 79 private static class Time implements Runnable { //用於統計時間 80 private long start ; 81 private String lockType; 82 83 public Time(String lockType, long start) { 84 this.start = start; 85 this.lockType = lockType; 86 } 87 88 public void run() { 89 System.out.println(lockType + "耗時:" + String.valueOf(System.currentTimeMillis() - start)); 90 } 91 } 92 }
首先執行非公平鎖,並使用"vmstat 1(每秒實時查看系統資源占用情況)",結果如下:
再執行公平鎖,並使用"vmstat 1(每秒實時查看系統資源占用情況)",結果如下:
通過上面的試驗結果可以得出結論,非公平鎖的性能因其系統上下文的切換較少,其性能一般要優於公平鎖。