【试验局】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(每秒实时查看系统资源占用情况)",结果如下:  

通过上面的试验结果可以得出结论,非公平锁的性能因其系统上下文的切换较少,其性能一般要优于公平锁。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏coolblog.xyz技术专栏

Java 线程同步组件 CountDownLatch 与 CyclicBarrier 原理分析

在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concu...

65913
来自专栏Linyb极客之路

并发编程之各种锁的简介

一、公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁。 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优...

3706
来自专栏Elasticsearch实验室

Elasitcsearch 底层系列 Lucene 内核解析之 Stored Fields

Lucene 的 stored fields 主要用于行存文档需要保存的字段内容,每个文档的所有 stored fields 保存在一起,在查询请求需要返回字段...

4725
来自专栏JackieZheng

探秘Tomcat——一个简易的Servlet容器

即便再简陋的服务器也是服务器,今天就来循着书本的第二章来看看如何实现一个servlet容器。 背景知识   既然说到servlet容器这个名词,我们首先要了解它...

2195
来自专栏JavaQ

高并发编程-ReentrantReadWriteLock深入解析

ReentrantLock在并发情况下只允许单个线程执行受保护的代码,而在大部分应用中都是读多写少,所以,如果使用ReentrantLock实现这种对共享数据的...

1093
来自专栏专注 Java 基础分享

并发编程之显式锁原理

Synchronized 关键字结合对象的监视器,JVM 为我们提供了一种『内置锁』的语义,这种锁很简便,不需要我们关心加锁和释放锁的过程,我们只需要告诉虚拟机...

912
来自专栏皮皮之路

【JDK1.8】JUC——AbstractQueuedSynchronizer

3398
来自专栏Linyb极客之路

并发编程之ReentrantLock

一、简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”。 ReentrantLock通过自定义队列同步器(AQS-Abstr...

3997
来自专栏皮皮之路

【JDK1.8】JUC——AbstractQueuedSynchronizer

1593
来自专栏小灰灰

Java并发学习之ReentrantLock的工作原理及使用姿势

Lock,ReentrantLock的工作原理及使用方式 jdk提供synchronized实现线程同步,但有些场景下并不灵活,如多个同步方法,每次只能有一个...

6626

扫码关注云+社区