概述
公平锁与非公平锁理论
- 前言
- AQS
- 公平锁
- 非公平锁
- ReentrantLock 使用
- ReentrantLock 解决饥饿效应
本文知识:
公平锁与非公平锁理论
AQS的理论与原理讲解
公平锁与非公平锁原理讲解
ReentrantLock的使用
非公平锁饥饿效应解决方法
在上篇中分析了Java并发编程中悲观锁与乐观锁的实现算法,并分析了原子类中使用CAS算法实现乐观锁以及ABA、自旋等问题。
延续上篇继续导论Java ReentrantLock锁,如果实现公平锁与非公平锁。
前言
公平锁:多个线程申请锁时是相对公平的,在申请锁时如果有其它线程已经占用了锁,则进行排队等待处理。
非公平锁:多个线程申请锁时相对不公平,与公平锁相同,都会存在排队的情况,但对于一个新线程需要获取锁时不是先排队等待,而是先尝试获取锁,不成功时再进入队列排队等待。
AQS
在分析公平锁与非公平锁之前,先来讲讲AQS这个家伙。
AQS全称AbstractQueuedSynchronizer,它实现了锁的等待队列、锁的获取与释放;它是一个抽象的实现。
AQS使用链接实现一个FIFO队列,使用state表示锁的状态,当state大于0时表示当前已经有线程占用锁了,在Thread1已经占用锁后再重复调用加锁操作时state会+1,这样就实现了重入锁,每次释放锁时state会-1,为0时完全释放锁,队列中等待的第一个线程可以开始获取锁。
AQS更多的内容后面再单独写一篇详细的文章进行分析,今天转入正题公平锁与非公平锁。
公平锁
线程在获取锁时以公平的形式进行,没有线程占用锁时可以直接获取锁成功,已经有线程占用锁或者已经有人在排队时将进入队列排队等待。公平锁不会出现饥饿效应,所有的线程都有可以获取到锁,但对CPU唤醒线程的开销较大,线程越多开销越大。下面用一个售票排队的例子来解释公平锁:
售票窗里的售票员负责卖票,相当于AQS中的state变量,一个购票人一次可以买n张票,出队了还需要买票就需要重复排队,新购票人来买票时看当前售票窗有没有人正在买票,没有就可以直接买票,有就需要排队,排队就相当于AQS中的FIFO队列。就样的过程就是公平锁实现的思想,票是资源由售票员进行管理与售卖,买票需要有秩序,不能插队,让所有买票的人都是公平的,谁先来谁先买。
非公平锁
线程获取锁时以不公平的方式进行,公平锁获取锁的方式是进入队列排队取锁,而非公平锁与公平锁在的区别只要就在排队上,非公平锁可以在队列的第一位的位置进行一次插队,插队成功就可以先获取锁,没有插队成功就需要像公平锁一样进行排队等待获取锁。非公平锁可能什么出现饥饿效应,队列中等待的线程与新来获取锁的线程在锁获取上是不公平的,有插队这一行为,正是因为有这一行为就有可能导致队列中等待的线程等待时间过长而出现一直未获取锁。虽然非公平锁有饥饿效应,但它相对于公平锁在获取锁的性能上更优,不会像公平锁一样每次都需要通知队列中的等待者去获取锁。下面还是通过图例来看看非公平锁的过程:
上图中还是使用购票这个例子,但排队的规则不一样了,现在可以进行一次插队,插队成功可以优先购票。
场景一:
新购票人4去购票,购票人刚好完成了购票并离开窗口,新购票人4进行插队,插队成功了,进行购票操作,购票完成后离开售票窗。
场景二:
新购票人4去购票,购票人刚好完成了购票并离开窗口,队列中等待购票人1优先站在了售票窗前进行购票操作,新购票人4没有插队成功,新购票人4只能去队列后面排队等待购票
因为有插队这一操作,排在最后的人等待时间会很长,在这炎炎烈日里心里有点怨气。
公平锁与非公平锁都有各自的优点与缺点,在Java中可以使用ReentrantLock类来使用公平锁与非公平锁
ReentrantLock 使用
//创建一个非公平锁
ReentrantLock nonfair=new ReentrantLock(false);
try {
//获取锁
if(nonfair.tryLock()){
System.out.println("获取非公平锁成功!");
}
}finally {
//释放锁
nonfair.unlock();
}
//创建一个公平锁
ReentrantLock fair=new ReentrantLock(true);
try {
//获取锁
if(fair.tryLock()){
System.out.println("获取公平锁成功!");
}
}finally {
//释放锁
fair.unlock();
}
ReentrantLock内部类Sync继承自AbstractQueuedSynchronizer类(AQS),实现了锁的基本功能,并使用FairSync内部类实现公平锁,使用NonfairSync实现非公平锁,这两个内部类都继承自Sync。
ReentrantLock 解决饥饿效应
使用非公平锁时可能会出现饥饿效应,这会导致某些线程一直不能获取到锁来进行后续任务的执行,这是生产环境不想看到的问题,如何解决?
饥饿效应产生的根本原因是线程在获取锁时在排队,而非公平锁使用了插队的方式来减少唤醒线程的CPU开销,插队导致后面的线程一直等待。这是饱效应的根本原因,根治的方法是让等待时间过长的线程有重新获取锁的机会,可以给每一个等待的线程一个超时时间,超过某一时间后可以重新获取一次锁,线程在获取锁的过程中加一个带有超时时间、自旋间隔的自旋逻辑。
ReentrantLock nonfair=new ReentrantLock(false);
try {
while (nonfair.tryLock(1,TimeUnit.SECONDS)) {
if (nonfair.isLocked()) {
System.out.println("获取公平锁成功!");
}
}
} catch (InterruptedException e) {
nonfair.unlock();
} finally {
nonfair.unlock();
}
往期推荐:
知识点: JAVA 悲观锁与乐观锁原理分析 ABA与自旋效率问题分析及解决
Java ThreadLocal 有泄漏内存的风险怎么搞?
知识点:Java sychronized 内部锁实现原理置顶
知识点: Java FutureTask 使用详解置顶
最后
以上就是寒冷鸡为你收集整理的知识点: Java公平锁与非公平锁 原理讲解ReentrantLock 锁的饥饿效应及解决办法的全部内容,希望文章能够帮你解决知识点: Java公平锁与非公平锁 原理讲解ReentrantLock 锁的饥饿效应及解决办法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复