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

  

  

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

  

时间: 2024-10-16 00:17:47

【试验局】ReentrantLock中非公平锁与公平锁的性能测试的相关文章

java多线程20 : ReentrantLock中的方法 ,公平锁和非公平锁

公平锁与非公平锁 ReentrantLock有一个很大的特点,就是可以指定锁是公平锁还是非公平锁,公平锁表示线程获取锁的顺序是按照线程排队的顺序来分配的,而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,先来的未必就一定能先得到锁,从这个角度讲,synchronized其实就是一种非公平锁.非公平锁的方式可能造成某些线程一直拿不到锁,自然是非公平的了.看一下例子,new ReentrantLock的时候有一个单一参数的构造函数表示构造的是一个公平锁还是非公平锁,传入true就可以了: publ

java多线程:并发包中ReentrantLock锁的公平锁原理

一:锁的原理结构 (1)锁对象内部维护了一个同步管理器的对象AbstractQueuedSynchronizer,AbstractOwnableSynchronizer (2)该对象其实是一个抽象类,具体的锁的管理器继承该抽象类 (3)该抽象类的关键属性有:---->Thread exclusiveOwnerThread(获取锁的线程对象) ----> Node head(首节点,正在拥有当前锁的线程构造的Node对象) ---->Node tail(尾巴节点,等待获取锁的线程构造的双向

锁的公平与非公平

锁是给线程用的,线程拿到锁之后才能干活.当多个线程竞争一个锁时,同一个时间只能有一个线程脱颖而出持有锁,其他线程等该线程释放锁后发起下一轮竞争.那么这种竞争就存在公平性问题,如果是公平的竞争,那么这些线程就得有序来依次得到锁,这就需要线程们按先来后到排队,第一个线程使用完后把锁递给第二个线程,以此类推.非公平的锁是无序的,举个例子:线程甲持有锁,后面线程乙跑过来争,发现被甲拿了就去睡觉(休眠或者被挂起)了,等线程甲释放锁通知乙来取:过了会儿,乙被唤醒时,刚好线程丙半路杀过来了,把锁拿去用了,而乙

多线程编程-- part5.1 互斥锁之公平锁-释放锁

释放公平锁 1.unlock() unlock()在ReentrantLock.java中实现的,源码如下: public void unlock() { sync.release(1); } 说明:unlock()是解锁函数,它是通过AQS的release()函数来实现的.在这里,“1”的含义和“获取锁的函数acquire(1)的含义”一样,它是设置“释放锁的状态”的参数.由于“公平锁”是可重入的,所以对于同一个线程,每释放锁一次,锁的状态-1. 2.release() release()在A

多线程编程-- part5.1 互斥锁之公平锁-获取锁

基本概念 1.AQS:AbstractQueuedSynchronizer类 AQS是java中管理“锁”的抽象类,锁的许多公共方法都是在这个类中实现.AQS是独占锁(例如,ReentrantLock)和共享锁(例如,Semaphore)的公共父类. (01) 独占锁 -- 锁在一个时间点只能被一个线程锁占有.根据锁的获取机制,它又划分为“公平锁”和“非公平锁”.公平锁,是按照通过CLH等待线程按照先来先得的规则,公平的获取锁:而非公平锁,则当线程要获取锁时,它会无视CLH等待队列而直接获取锁.

synchronized与lock 对象锁、互斥锁、共享锁以及公平锁和非公平锁

synchronized与lock  都是用来实现线程同步的锁,synchronized对象锁,lock是一个接口,她的实现有reentrantlock互斥锁以及ReentrantReadWriteLock共享锁. 这里说明一下ReentrantReadWriteLock共享锁,所谓共享就是该锁提供读读锁共享,即可以多个线程共享一个读取锁,但是读写锁以及读读锁是互斥的. 看到网上有好多介绍介绍锁的种类的,有对象锁.互斥锁.共享锁以及公平锁和非公平锁,但是说明都不够详细了然,在这里用直白的说法记录

公平锁,非公平锁,乐观锁,悲观锁

公平锁/非公平锁(多线程执行顺序的维度) 概念理解 公平锁:加锁前先查看是否有排队等待的线程,有的话优先处理排在前面的线程,先来先得. 非公平所:线程加锁时直接尝试获取锁,获取不到就自动到队尾等待. 例子 ReentrantLock 同时支持两种锁 //创建一个非公平锁,默认是非公平锁 Lock nonFairLock= new ReentrantLock(); Lock nonFairLock= new ReentrantLock(false); //创建一个公平锁,构造传参true Lock

最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁

在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 01.乐观锁 vs 悲观锁 乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度,在Java和数据库中都有此概念对应的实际应用. 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制. 乐观锁适用于多

Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等(转)

Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁 / 非公平锁 可重入锁 / 不可重入锁 独享锁 / 共享锁 互斥锁 / 读写锁 乐观锁 / 悲观锁 分段锁 偏向锁 / 轻量级锁 / 重量级锁 自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释. 公平锁 / 非公平锁 公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公