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 锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!