​​​​​​​Java公平锁和非公平锁的区别

 更新时间:2022年05月10日 16:41:49   作者:​ Java中文社群   ​  
本文介绍​​​​​​​Java公平锁和非公平锁区别,公平锁是每个线程获取锁顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁;而非公平锁是每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁,下文内容需要的朋友可以参考下

前言:

从公平的角度来说,Java 中的锁总共可分为两类:公平锁和非公平锁。但公平锁和非公平锁有哪些区别?孰优孰劣呢?在 Java 中的应用场景又有哪些呢?接下来我们一起来看。

正文

公平锁:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。
非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁。

 举个例子:公平锁就像开车经过收费站一样,所有的车都会排队等待通过,先来的车先通过,

如下图所示:

通过收费站的顺序也是先来先到,分别是张三、李四、王五,这种情况就是公平锁。 而非公平锁相当于,来了一个强行加塞的老司机,它不会准守排队规则,来了之后就会试图强行加塞,如果加塞成功就顺利通过,当然也有可能加塞失败,如果失败就乖乖去后面排队,这种情况就是非公平锁。 

应用场景

在 Java 语言中,锁 synchronized 和 ReentrantLock 默认都是非公平锁,当然我们在创建 ReentrantLock 时,可以手动指定其为公平锁,但 synchronized 只能为非公平锁

 ReentrantLock 默认为非公平锁可以在它的源码实现中得到验证,如下源码所示: 

 当使用 new ReentrantLock(true) 时,可以创建公平锁,如下源码所示: 

公平和非公平锁代码演示

接下来我们使用 ReentrantLock 来演示一下公平锁和非公平锁的执行差异,首先定义一个公平锁,开启 3 个线程,每个线程执行两次加锁和释放锁并打印线程名的操作,

如下代码所示:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockFairTest {
    static Lock lock = new ReentrantLock(true);
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                for (int j = 0; j < 2; j++) {
                    lock.lock();
                    System.out.println("当前线程:" + Thread.currentThread()
                            .getName());
                    lock.unlock();
                }
            }).start();
        }
    }
}

以上程序的执行结果如下图所示: 

 接下来我们使用非公平锁来执行上面的代码,具体实现如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockFairTest {
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                for (int j = 0; j < 2; j++) {
                    lock.lock();
                    System.out.println("当前线程:" + Thread.currentThread()
                            .getName());
                    lock.unlock();
                }
            }).start();
        }
    }
}

以上程序的执行结果如下图所示: 

从上述结果可以看出,使用公平锁线程获取锁的顺序是:A -> B -> C -> A -> B -> C,也就是按顺序获取锁。而非公平锁,获取锁的顺序是 A -> A -> B -> B -> C -> C,原因是所有线程都争抢锁时,因为当前执行线程处于活跃状态,其他线程属于等待状态(还需要被唤醒),所以当前线程总是会先获取到锁,所以最终获取锁的顺序是:A -> A -> B -> B -> C -> C。

执行流程分析

公平锁执行流程

获取锁时,先将线程自己添加到等待队列的队尾并休眠,当某线程用完锁之后,会去唤醒等待队列中队首的线程尝试去获取锁,锁的使用顺序也就是队列中的先后顺序,在整个过程中,线程会从运行状态切换到休眠状态,再从休眠状态恢复成运行状态,但线程每次休眠和恢复都需要从用户态转换成内核态,而这个状态的转换是比较慢的,所以公平锁的执行速度会比较慢。

非公平锁执行流程

当线程获取锁时,会先通过 CAS 尝试获取锁,如果获取成功就直接拥有锁,如果获取锁失败才会进入等待队列,等待下次尝试获取锁。这样做的好处是,获取锁不用遵循先到先得的规则,从而避免了线程休眠和恢复的操作,这样就加速了程序的执行效率。

公平锁和非公平锁的性能测试结果如下:

 从上述结果可以看出,使用非公平锁的吞吐率(单位时间内成功获取锁的平均速率)要比公平锁高很多。

优缺点分析

公平锁的优点是按序平均分配锁资源,不会出现线程饿死的情况,它的缺点是按序唤醒线程的开销大,执行性能不高。 非公平锁的优点是执行效率高,谁先获取到锁,锁就属于谁,不会“按资排辈”以及顺序唤醒,但缺点是资源分配随机性强,可能会出现线程饿死的情况。

总结

在 Java 语言中,锁的默认实现都是非公平锁,原因是非公平锁的效率更高,使用 ReentrantLock 可以手动指定其为公平锁。非公平锁注重的是性能,而公平锁注重的是锁资源的平均分配,所以我们要选择合适的场景来应用二者。

到此这篇关于Java公平锁和非公平锁的区别的文章就介绍到这了,更多相关Java 锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springboot结合maven实现多模块打包

    springboot结合maven实现多模块打包

    本文主要介绍了springboot借助maven完成多模块打包,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Java利用EasyExcel解析动态表头及导出实现过程

    Java利用EasyExcel解析动态表头及导出实现过程

    以前做导出功能,表头和数据都是固定的,下面这篇文章主要给大家介绍了关于Java利用EasyExcel解析动态表头及导出实现的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • Spring Boot中的微信支付全过程(小程序)

    Spring Boot中的微信支付全过程(小程序)

    微信支付是企业级项目中经常使用到的功能,作为后端开发人员,完整地掌握该技术是十分有必要的。今天通过本文给大家介绍Spring Boot中的微信支付全过程,感兴趣的朋友一起看看吧
    2022-05-05
  • 解决@Autowired注入static接口的问题

    解决@Autowired注入static接口的问题

    这篇文章主要介绍了解决@Autowired注入static接口的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • springmvc fastjson 反序列化时间格式化方法(推荐)

    springmvc fastjson 反序列化时间格式化方法(推荐)

    下面小编就为大家带来一篇springmvc fastjson 反序列化时间格式化方法(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • 详解Java编写算法时如何加快读写数据速度

    详解Java编写算法时如何加快读写数据速度

    这篇文章主要为大家详细介绍了Java在编写算法时如何加快读写数据速度,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • Java使用数组实现ArrayList的动态扩容的方法

    Java使用数组实现ArrayList的动态扩容的方法

    这篇文章主要介绍了Java使用数组实现ArrayList的动态扩容的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • Java实现最小生成树算法详解

    Java实现最小生成树算法详解

    这篇文章主要介绍了如何在Java中实现最小生成树算法,文中的示例代码讲解详细,对我们学习Java有一定帮助,需要的可以参考一下
    2022-04-04
  • Spring Cache相关知识总结

    Spring Cache相关知识总结

    今天带大家学习Spring的相关知识,文中对Spring Cache作了非常详细的介绍,对正在学习Java Spring的小伙伴们很有帮助,需要的朋友可以参考下
    2021-05-05
  • Spring中Bean的生命周期及实例化操作详解

    Spring中Bean的生命周期及实例化操作详解

    这篇文章主要介绍了Spring中Bean的生命周期及实例化操作详解,spring的核心思想之一IOC就是通过IOC容器对Bean的创建和各个bean之间的依赖关系进行操作,今天就来和大家分享一下bean的生命周期相关知识点,需要的朋友可以参考下
    2023-08-08

最新评论