Java ReentrantLock 不看后悔

1,362 阅读2分钟

提示:如有疑问请私信联系、下方有源代码地址,请自行拿取


前言

面试中经常会问到公平锁与非公平锁的区别,这篇文章我相信看了之后你定能消灭这个知识盲区


提示:以下是本篇文章正文内容,下面案例可供参考

一、技术介绍

1.ReentrantLock是什么?

ReentrantLock实现了Lock接口,ReentrantLock具有更好的细粒度,可以在ReentrantLock里面设置内部Condititon类,可以实现分组唤醒需要唤醒的线程,synchronized是非公平锁,而RenentrantLock既能实现非公平锁也能实现公平锁

二、源码分析

1.非公平锁

直接看ReentrantLock默认非公平锁源码

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

重点看红框中圈出的代码,可以看到非公平锁里,判断当前锁占用状态==0直接会进行compareAndSetState尝试获取锁。若此时有线程排队,可能争夺不过资源。所以这是非公平的 在非公平锁里,因为可以直接compareAndSetState来获取锁,不需要加入队列,然后等待队列头线程唤醒再获取锁这一步骤,所以效率较公平锁快

2.公平锁

在接着看ReentrantLock公平锁源码 process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMxNjE2NTY3,size_16,color_FFFFFF,t_70) 在公平锁中,判断当前锁占用状态等于0后,会继续判断hasQueuedPredecessors队列是否有排队的情况,如果没有才会尝试获取锁,这样可以保证FIFO原则,每一个先来的线程都可以最先获取到锁,但是增加了上下文切换与等待线程的状态变换时间,效率相较于非公平锁慢。

三、单元测试

新建 ReentrantLockTest 类 先来看公平锁的测试代码

	  @Test
    public void testLock() throws InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        //公平锁测试
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new FairLockTest());
        }

        //非公平锁测试
//        for (int i = 0; i < 10; i++) {
//            threadPool.execute(new NonFairLockTest());
//        }
        threadPool.shutdown();
        TimeUnit.SECONDS.sleep(5);
    }
  class FairLockTest implements Runnable {
    /**
     * 公平锁
     */
    Lock fairLock = new ReentrantLock(true);

    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getId() + "启动");
        try {
            fairLock.lock();
            System.out.println("线程" + Thread.currentThread().getId() + "获得锁");
        } finally {
            System.out.println("线程" + Thread.currentThread().getId() + "释放锁--------");
            fairLock.unlock();
        }
    }
}

看下执行结果

在这里插入图片描述

可以看到获取锁的顺序基本和线程启动顺序一致,那红框标记的我为什么框出来呢?就是想说明一点公平只是相对的公平,所有公平模式,就是如果前面有线程排队就加入队尾,但是线程本身的执行是没有顺序和优先级的,这里要划重点。

再看看非公平锁的测试代码

	@Test
    public void testLock() throws InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        //公平锁测试
//        for (int i = 0; i < 10; i++) {
//            threadPool.execute(new FairLockTest());
//        }

        //非公平锁测试
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new NonFairLockTest());
        }
        threadPool.shutdown();
        TimeUnit.SECONDS.sleep(5);
    }
class NonFairLockTest implements Runnable {
    /**
     * 非公平锁
     */
    Lock nonfairLock = new ReentrantLock(false);

    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getId() + "启动");
        try {
            nonfairLock.lock();
            System.out.println("线程" + Thread.currentThread().getId() + "获得锁");
        } finally {
            System.out.println("线程" + Thread.currentThread().getId() + "释放锁--------");
            nonfairLock.unlock();
        }
    }
}

直接看测试结果

在这里插入图片描述

可以看到,出现了后面启动的可能先获得锁的比较多,这里只开了10个线程可能不明显,当在高并发情况下可能会出现先启动的线程一直获取不到锁的情况。

总结

虽公平锁与非公平锁都有其应用场景

公平锁:相对获取锁公平,如果业务中线程处理时间要远长于线程等待,那用非公平锁其实效率并不明显,用公平锁会给业务增强很多的可控制性。

非公平锁:性能高于公平锁,首先,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟,而且非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。

作者寄语

是不是感觉很简单?更多用法请点击下方查看源码,关注我带你揭秘更多高级用法

源码地址:点此查看源码.