有个小伙伴最近咨询我,前段时间他被面试官问了synchronized是公平锁仍是非公平锁?当时就蒙圈了,最后面试结果可想而知,今天咱们就用一个通俗的案例加上代码来讲明公平锁和非公平锁。其实公平锁这个概念是JUC工具包才有的,好比ReentrantLock才有公平锁的概念,这篇文章咱们结合生活中的实例用2段代码说明ReentrantLock公平锁和非公平锁,以及证实synchronized是非公平锁的。但愿对小伙伴有帮助。java
/** * @author :jiaolian * @date :Created in 2020-12-31 16:01 * @description:食堂打饭:synchronized不公平 * @modified By: * 公众号:叫练 */ public class SyncUnFairLockTest { //食堂 private static class DiningRoom { //获取食物 public void getFood() { System.out.println(Thread.currentThread().getName()+":排队中"); synchronized (this) { System.out.println(Thread.currentThread().getName()+":@@@@@@打饭中@@@@@@@"); } } } public static void main(String[] args) { DiningRoom diningRoom = new DiningRoom(); //让5个同窗去打饭 for (int i=0; i<5; i++) { new Thread(()->{ diningRoom.getFood(); },"同窗编号:00"+(i+1)).start(); } } }
如上代码:咱们定义一个内部类DiningRoom表示食堂,getFood方法里面用synchronized锁修饰this指向DiningRoom的实例对象(22行中的diningRoom对象),主类中让编号001至005五个同窗同时去打饭,用于测试先排队的同窗是否能先打到饭?运行程序获得其中一种执行结果以下图所示,002->004->001->003->005同窗先去排队,但打饭的顺序是002->003->001->004->005,说明这里003和001两个同窗插队了,插到004前面了,咱们详细分析执行过程,002先抢到锁打饭了,释放了锁,原本应该是接下来是004抢到锁去打饭(由于004是比003先来排队),但003抢到锁,打饭了,释放了锁,这是第一次插队。如今仍是来004抢锁,可是没抢到又被001抢到了,释放锁后才被004抢到,这是第二次插队,后面分别再是004->005抢到锁,释放锁,程序执行完毕。由于003和001插队,咱们用代码证实了synchronized是非公平锁。紧接着咱们来看下ReentrantLock公平锁和非公平锁。c++
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author :jiaolian * @date :Created in 2020-12-31 11:11 * @description:非公平锁测试 在获取锁的时候和再获取锁的顺序不一致; * @modified By: * 公众号:叫练 */ public class UnFairLockTest { private static final Lock LOCK = new ReentrantLock(false); //食堂 private static class DiningRoom { //获取食物 public void getFood() { try { System.out.println(Thread.currentThread().getName()+":正在排队"); LOCK.lock(); System.out.println(Thread.currentThread().getName()+":@@@@@@打饭中@@@@@@@"); } catch (Exception e) { e.printStackTrace(); } finally { LOCK.unlock(); } } } public static void main(String[] args) throws InterruptedException { DiningRoom diningRoom = new DiningRoom(); //让5个同窗去打饭 for (int i=0; i<5; i++) { new Thread(()->{ diningRoom.getFood(); },"同窗编号:00"+(i+1)).start(); } } }
如上代码:咱们在代码中定义了Lock LOCK = new ReentrantLock(false);ReentrantLock的参数是false表示非公平锁,上面代码须要用LOCK.lock()加锁,LOCK.unlock()解锁,须要放入try,finally代码块中,目的是若是try中加锁后代码发生异常锁最终执行LOCK.unlock(),锁总能被释放。主类中让编号001至005五个同窗同时去打饭,获得其中一种执行结果以下图所示,001->004->005->003->002同窗先去排队,但打饭的顺序是001->005->004->003->002,这里005同窗插队了,插到004前面。咱们详细分析执行过程:001先来抢到锁打饭了并释放了锁,接下来本应该是004抢到锁,由于它先排队,但005却在004以前抢到锁,打饭了,005比004后来,却先打饭,这就是不公平锁,后面的执行结果按先来后到执行,程序结束。咱们用代码证实了ReentrantLock是非公平的锁。紧接着咱们来看下ReentrantLock另外一种做为公平锁的状况。面试
基于上面的案例,咱们不重复贴代码了,将上述代码中的private static final Lock LOCK = new ReentrantLock(false);参数由false改成true,private static final Lock LOCK = new ReentrantLock(true);不管执行多少次能够得出一个结论:先排队的童鞋能先打饭,不容许插队体现的就是公平锁。多线程
ReentrantLock是基于AbstractQueuedSynchronizer(抽象队列同步器,简称aqs)实现的,aqs底层维护了一个带头的双向链表,用来同步线程,链表每一个节点用Node表示,每一个Node会记录线程信息,上下节点,节点状态等信息,aqs控制Node的生命周期。以下图所示,aqs也包含条件队列,锁和条件队列(condition)是一对多的关系,也就是说一个锁能够对应多个条件队列,线程间的通讯在条件队列里经过await,single/singleAll方法控制,synchronized只有一个条件队列用wait,notify/notifyAll来实现,这里不展开说了,《母鸡下蛋实例:多线程通讯生产者和消费者wait/notify和condition/await/signal条件队列》和《Synchronized用法原理和锁优化升级过程(面试)》能够看我文章,里面有大量清晰简单案例。条件队列也是以链表形式存在。Lock是基于juc包实现,synchronized是本地方法基于c++实现。ide
今天用生活中的例子转化成代码,详细的介绍了公平锁和非公平锁,并简单的介绍了aqs实现原理,给您的建议是认真把代码敲一遍,若是执行了一遍代码应该能看明白,喜欢的请点赞加关注哦。我是叫练【公众号】,边叫边练。工具