概念解释:
公平锁,指的是在进行锁的竞争中,没有获得锁的线程,当锁被释放时,会按照竞争线程的先后顺序依次进行锁的获取,先到先得,体现公平性;
非公平锁,指的是锁释放后,竞争锁的线程不会按照先后顺序来获取被释放的锁,结果具有随机性,故不体现公平性;
实例分析: 以ReentrantLock 为例,分别实现公平锁及非公平锁案例;
/*** * 公平锁实现: * ReentrantLock 构造方法中可以设置为true,代表公平锁,false,代表非公平锁; * * 实例逻辑: * 主线程占用锁,并创建10个子线程分别命名为1-10,依次竞争主线程的锁,主线程释放,查看子线程名称打印结果是否有序; * * (注:这里的代码暂时认为锁的释放前不会出现异常,但是实际开发中释放锁的操作可能需要放到finally中;) * * */ public class FairLock { private static ReentrantLock reentrantLock = new ReentrantLock(true); public static void main(String[] args) { System.out.println(" -- Main Thread Start -- "); reentrantLock.lock(); for(int i=1;i<=10;i++){ new Thread(new Runnable() { @Override public void run() { reentrantLock.lock(); try { Thread.sleep(100); //给打印加一点缓冲时间,确保正确性 }catch (InterruptedException e){ System.out.println(" -- InterruptedException Happend -- "); } System.out.println(" -- Get Lock Thread, Name: " +Thread.currentThread().getName() +" -- "); reentrantLock.unlock(); } },"Thead: " + i).start(); } reentrantLock.unlock(); System.out.println(" -- Main Thread End -- "); } }
结果对比:针对非公平锁,只需修改上述代码中ReentrantLock 构造方法的参数为false,所以这边代码不做重复展示,只分别列出公平锁及非公平锁的结果(分别运行3次以上);
如上实例中,公平锁和非公平锁的结果多次运行后会有不一致的,那么公平锁和非公平锁究竟如何实现,非公平锁如何具有随机性,我们需要从源码出发!
源码分析: 本博客暂不对CAS操作,AQS,自旋线程管理队列数据结构等实现锁的基本原理进行分析,如有不熟悉的,可以先行了解一下,后续也会出博客说明锁原理的实现,本次着重看待在公平锁和非公平锁在ReentrantLock中的不同之处。
自定义 FairLock 执行代码的UML核心调用图如下:
FairLock 流程分析: 当线程调用公平锁lock时,会直接调用 AQS类中acquire的方法,tryAcquire 使用的是公平锁内部实现,尝试获取锁,如果获取锁失败以后会调用 acquireQueued(addWaiter())方法将线程数据添加到双向链表尾部并自旋获取锁。
这里有必要先着重强调一下acqurieQuequed方法,由源码可以看出,添加到链表内的Node数据,仅最开始的元素才能有竞争锁的资格,所以获取锁失败并添加到链表中的线程是按照添加顺序进行依次再获取锁。因而我们看待锁的公平和非公平实际看待的是锁释放后,获取锁的线程和已经添加到内部链表的线程的竞争顺序;
再看tryAcquire 的源码实现,可以看出在CAS操作获取锁之前调用了一个方法hasQueuePredecessors,该方法用于判断是否有线程比当前线程等待的时间更久,也就是还未添加到链表的线程和链表头部线程之间竞争关系的判断,同时获取锁的时候,始终是链表头部的线程拿到锁,体现公平性;
自定义 NonFairLock 执行代码的UML核心调用图如下:
NonFairLock 流程分析:线程调用非公平锁lock时,和公平锁实现略有不同,他会先进行一次CAS获取锁操作,失败后后会再尝试获取锁,等于尝试两次获取锁,这里是非公平的核心,他会让还未添加到链表的线程和链表头部线程进行直接竞争,导致不一定链表头部线程会获取到锁的现象,因此体现不公平,至于为什么是尝试两次,我想是因为尽最大可能性避免自旋产生吧。如果线程仍然获取不到锁,再进行添加队列等操作,这里和公平锁的实现是一样的。
如有任何问题,欢迎指正讨论,谢谢!