硬件环境:
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(每秒实时查看系统资源占用情况)",结果如下:
通过上面的试验结果可以得出结论,非公平锁的性能因其系统上下文的切换较少,其性能一般要优于公平锁。