专栏首页余林丰【试验局】ReentrantLock中非公平锁与公平锁的性能测试

【试验局】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 条评论
登录 后参与评论

相关文章

  • 1.有关线程、并发的基本概念

    什么是线程?   提到“线程”总免不了要和“进程”做比较,而我认为在Java并发编程中混淆的不是“线程”和“进程”的区别,而是“任务(Task)”。进程是表示资...

    用户1148394
  • 12.ThreadPoolExecutor线程池原理及其execute方法

    jdk1.7.0_79   对于线程池大部分人可能会用,也知道为什么用。无非就是任务需要异步执行,再者就是线程需要统一管理起来。对于从线程池中获取线程,大部分...

    用户1148394
  • 虚拟机类加载机制(3)——线程上下文类加载器

    之所以将线程上下文类加载器(Thread Context ClassLoader)单独拿出来写,确实是因为它涉及的东西比较多,既然带有线程两个字,一定也是非常重...

    用户1148394
  • ZooKeeper分布式锁应用

    Zookeeper是一个高性能的分布式系统的协调服务。它在一个简单的接口里暴露公共服务:像命名、配置管理、同步、和群组服务,所以你没有必要从头开始实现它们。你可...

    会跳舞的机器人
  • 慕课网高并发实战(七)- J.U.C之AQS

    3.不断重新尝试获取锁(当前结点为head的直接后继才会 尝试),如果获取失败,则会阻塞自己,直到被唤醒

    Meet相识
  • JDK动态代理代理与Cglib代理原理探究

    UserServiceImpl被JDK代理后的类,在项目的com.sun.proxy下面生成$Proxy0.class类

    加多
  • 平台用户操作日志模块设计

    用户1418372
  • iOS卡顿监控方案浅析

    最近,小编一直致力于解决一项性能问题,那就是iOS输入法输入卡顿问题的监控,通过一段时间的调研,小编整理出来了一些监控方法,这里就分享给大家,希望可以给正在进行...

    用户5521279
  • Java多线程学习(一)Java多线程入门

    Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):https://github.com/Snailclimb/Java_G...

    用户2164320
  • 处理器的乱序执行

    233333

扫码关注云+社区

领取腾讯云代金券