类结构
在ReentrantLock中,有个内部类Sync继承了AbstractQueuedSynchronizer类
Sync在ReentrantLock类中有两个子类FairSync和NonFairSync,也就是公平锁与非公平锁。
公平锁与非公平锁的加锁方式的区别表现在哪里?
首先看lock()方法的区别:
- 非公平锁
- 公平锁:
两段代码的主要区别是非公平锁会首先尝试获取锁,没有获取到再去执行acquire()方法,而公平锁直接执行acquire()
下面我们来看acquire()
acquire()是Sync从AbstractQueuedSynchronizer中继承的
并且没有重写,所以公平锁与非公平锁的acquire()方法是一样的
接着,我们来分析上图中标记出来的三个方法
- 首先执行了tryAcquire方法,该方法的作用是尝试获取锁,在公平锁与非公平锁中有不同的实现
- 非公平锁
- 公平锁
两段代码的逻辑基本上一致,唯一的不同在于tryAcquire()比nonfairTryAcquire()增加了
hasQueuedPredecessors的逻辑判断,方法名就可知道该方法用来判断当前节点在
同步队列中是否有前驱节点的判断,如果有前驱节点说明有线程比当前线程更早的请
求资源,根据公平性,当前线程请求资源失败。如果当前节点没有前驱节点的话,才
有做后面的逻辑判断的必要性
2、如果tryAcquire没有获取到锁,则继续执行addWaiter()方法,该方法的主要作用就是尝试将当前线程快速的加入到同步等待队列,目的是降低CAS和自旋的成本,如果加入失败,则执行enq()方法
通过CAS加自旋操作进入队列
3、加入队列之后,执行acquireQueued()方法
acquireQueued()方法的作用是检查当前线程节点是否在队列首部,如果在,则再次执行tryAcquire()尝试获取锁,如果不在,将会给该节点的前驱节点添加唤醒改节点的职责,在最多执行3次自旋后,如果还没有获取到锁,那么该线程被parkAndCheckInterrupt()方法挂起
以上就是公平锁与非公平锁在获取锁的方式上的区别,文章表述上可能存在问题,但整体思路应该正确,如果有不对的地方,请大家多多指教
总结:
公平锁:在线程尝试获取公平锁时,先去判断队列中是否有前驱节点,如果没有前驱节点,尝试去获取一次锁,如果获取失败,把线程封装成节点放入队列中,然后该线程会进行最多3次自旋,判断线程是否在队列首部,如果在,尝试获取锁。在自旋次数耗尽后,线程仍未获取到锁,则线程被挂起。
非公平锁:在线程尝试获取非公平锁时,直接尝试获取锁,如果没有获取到,就把该线程封装成节点放到队列中,然后该线程会进行最多3次自旋,判断线程是否在队列首部,如果在,尝试获取锁。如果在自旋次数耗尽后,线程仍未获取到锁,则线程被挂起。
也就是说公平锁会比非公平锁多一个对队列中是否有前驱节点的判断
公平锁 VS 非公平锁的优缺点
1.公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。
2.公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。