公平锁与非公平锁的对比

扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,便可关注微信公众号,阅读更多Spring源码分析Java并发编程文章。java

微信公众号

1. 问题

  • 在上一篇文章中结合源码介绍了公平锁和非公平锁的实现【文章连接】。这一篇文章将从公平性和性能方面对比一下二者。
  • 在阅读本文以前,能够先思考一下下面两个问题。
  • 1. 非公平锁必定不公平吗?
  • 2. 公平锁与非公平锁的性能谁更好?

2. 对比

  • 主要从公平性和性能这两个方面来对比一下公平锁和非公平锁。

2.1 公平性

  • 在上一篇文章的总结处,提到了公平锁和非公平锁是从线程获取锁所等待的时间来区分二者的公平性。公平锁中,多个线程抢锁时,获取到锁的线程必定是同步队列中等待时间最长的线程。而非公平锁中,多个线程抢锁时,获取锁的线程不必定是同步队列中等待时间最长的线程,有多是同步队列以外的线程先抢到锁。
  • 对于公平锁,线程获取锁的过程能够用以下示意图表示(图片来源于公众号:Mr羽墨青衫,文章连接:深刻剖析Java重入锁ReentrantLock的实现原理)。

公平锁抢锁示意图

  • 从图中能够发现,当持有锁的线程T1释放锁之后,会唤醒同步队列中的T2线程,只要同步队列中有线程在等待获取锁,那么其余刚进来想要获取锁的人,就不能插队,包括T1本身还想要获取锁,也须要去排队,这样就保证了让等待时间最长的线程获取到锁,即保证了公平性。编程

  • 对于非公平锁,线程获取锁的示意图能够用以下示意图表示。(图片来源于公众号:Mr羽墨青衫,文章连接:深刻剖析Java重入锁ReentrantLock的实现原理)。设计模式

非公平锁抢锁示意图

  • 从图中能够发现,当持有锁的线程T1释放锁之后,会唤醒同步队列中的T2线程,此时即便同步队列中有线程在排队,从外面刚进来的线程想要获取锁,此时是能够直接去争抢锁的,包括线程T1本身,这个时候就是T二、T一、外面刚进来的线程一块儿去抢锁,虽然此时T2线程等待的时间最长,可是不能保证T2必定抢到锁,因此此时是不公平的。
  • 文章开头提到了一个问题,非公平锁必定不公平吗?答案是不必定,对于刚刚上面图中展现的那种状况,此时非公平锁是不公平的。可是存在一种特殊状况,能够参考以下示意图,如当T1线程释放锁之后,AQS同步队列外部没有线程来争抢锁,T1线程在释放锁之后,本身也不须要获取锁了,此时T1由于唤醒的是T2,如今是有T2一个线程来抢锁,因此此时能获取到锁的线程必定是T2,这种状况下,非公平锁又是公平的了,由于此时即便同步队列中有T三、T四、...、Tn线程,可是T2等待的时间最长,因此是T2获取到锁(AQS的同步队列遵循的原则是FIFO)。

非公平锁抢锁特殊场景

  • 从非公平锁的示意图中,咱们也能够发现,若是线程一旦获取锁失败进入到AQS同步队列后,就会一直排队,直到其余获取到锁的线程唤醒它或者它被其余线程中断,才会出队。即:一朝排队,永远排队。

性能

  • 公平锁和非公平锁的性能是不同的,非公平锁的性能会优于公平锁。为何呢?由于公平锁在获取锁时,永远是等待时间最长的线程获取到锁,这样当线程T1释放锁之后,若是还想继续再获取锁,它也得去同步队列尾部排队,这样就会频繁的发生线程的上下文切换,当线程越多,对CPU的损耗就会越严重。
  • 非公平锁性能虽然优于公平锁,可是会存在致使线程饥饿的状况。在最坏的状况下,可能存在某个线程一直获取不到锁。不过相比性能而言,饥饿问题能够暂时忽略,这可能就是ReentrantLock默认建立非公平锁的缘由之一了。
  • 下面以一个demo为例,对比了一下公平锁与非公平锁的性能。
public class Demo {

    // 公平锁
    private static Lock fairLock = new ReentrantLock(true);

    // 非公平锁
    private static Lock nonFairLock = new ReentrantLock(false);

    // 计数器
    private static int fairCount = 0;

    // 计数器
    private static int nonFairCount = 0;

    public static void main(String[] args) throws InterruptedException {
        System.out.println("公平锁耗时: " + testFairLock(10));
        System.out.println("非公平锁耗时: " + testNonFairLock(10));
        System.out.println("公平锁累加结果: " + fairCount);
        System.out.println("非公平锁累加结果: " + nonFairCount);
    }

    public static long testFairLock(int threadNum) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        // 建立threadNum个线程,让其以公平锁的方式,对fairCount进行自增操做
        List<Thread> fairList = new ArrayList<>();
        for (int i = 0; i < threadNum; i++) {
            fairList.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    fairLock.lock();
                    fairCount++;
                    fairLock.unlock();
                }
                countDownLatch.countDown();
            }));
        }

        long startTime = System.currentTimeMillis();
        for (Thread thread : fairList) {
            thread.start();
        }
        // 让全部线程执行完
        countDownLatch.await();
        long endTime = System.currentTimeMillis();

        return endTime - startTime;
    }

    public static long testNonFairLock(int threadNum) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        // 建立threadNum个线程,让其以非公平锁的方式,对nonFairCountCount进行自增操做
        List<Thread> nonFairList = new ArrayList<>();
        for (int i = 0; i < threadNum; i++) {
            nonFairList.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    nonFairLock.lock();
                    nonFairCount++;
                    nonFairLock.unlock();
                }
                countDownLatch.countDown();
            }));
        }
        long startTime = System.currentTimeMillis();
        for (Thread thread : nonFairList) {
            thread.start();
        }
        // 让全部线程执行完
        countDownLatch.await();
        long endTime = System.currentTimeMillis();

        return endTime - startTime;
    }
}
复制代码
  • 上面的Demo中,建立了threadNum个线程,而后让这threadNum个线程并发的对变量进行累加10000次的操做,分别用公平锁和非公平锁来保证线程安全,最后分别统计出公平锁和非公平锁的耗时结果。
  • threadNum = 10时,重复三次测试的结果以下。
次数 公平锁 非公平锁
1 618ms 22ms
2 544ms 20ms
3 569ms 15ms
  • threadNum = 20时,重复三次测试的结果以下。
次数 公平锁 非公平锁
1 1208ms 25ms
2 1146ms 26ms
3 1215ms 19ms
  • threadNum = 30时,重复三次测试的结果以下。
次数 公平锁 非公平锁
1 1595ms 28ms
2 1543ms 31ms
3 1601ms 31ms
  • 测试环境:macOS 10.14.6,处理器:2.9GHZ,Intel Core i7,内存:16G 2133MHz
  • 从上面的测试结果能够发现,非公平锁的耗时远远小于公平锁的耗时,这说明非公平锁在并发状况下,性能更好,吞吐量更大。当线程数越多时,差别越明显。

相关推荐

微信公众号
相关文章
相关标签/搜索