知识点:Java公平锁与⾮公平锁原理讲解ReentrantLock锁的饥饿效应及解决办
法
公平锁与⾮公平锁理论
本⽂知识:
公平锁与⾮公平锁理论
AQS的理论与原理讲解
公平锁与⾮公平锁原理讲解
ReentrantLock的使⽤
地址 英文
⾮公平锁饥饿效应解决⽅法
在上篇中分析了Java并发编程中悲观锁与乐观锁的实现算法,并分析了原⼦类中使⽤CAS算法实现乐观锁以及ABA、⾃旋等问题。
延续上篇继续导论Java ReentrantLock锁,如果实现公平锁与⾮公平锁。
前⾔
公平锁:多个线程申请锁时是相对公平的,在申请锁时如果有其它线程已经占⽤了锁,则进⾏排队等待处理。
⾮公平锁:多个线程申请锁时相对不公平,与公平锁相同,都会存在排队的情况,但对于⼀个新线程需要获取锁时不是先排队等待,⽽是先尝试获取锁,不成功时再进⼊队列排队等待。
AQS
在分析公平锁与⾮公平锁之前,先来讲讲AQS这个家伙。
AQS全称AbstractQueuedSynchronizer,它实现了锁的等待队列、锁的获取与释放;它是⼀个抽象的实现。
soursopAQS使⽤链接实现⼀个FIFO队列,使⽤state表⽰锁的状态,当state⼤于0时表⽰当前已经有线程占⽤锁了,在Thread1已经占⽤锁后再重复调⽤加锁操作时state会+1,这样就实现了重⼊锁,每次释放锁时state会-1,为0时完全释放锁,队列中等待的第⼀个线程可以开始获取锁。
宝贝英文翻译
AQS更多的内容后⾯再单独写⼀篇详细的⽂章进⾏分析,今天转⼊正题公平锁与⾮公平锁。
公平锁
线程在获取锁时以公平的形式进⾏,没有线程占⽤锁时可以直接获取锁成功,已经有线程占⽤锁或者已经有⼈在排队时将进⼊队列排队等待。公平锁不会出现饥饿效应,所有的线程都有可以获取到锁,但对CPU唤醒线程的开销较⼤,线程越多开销越⼤。下⾯⽤⼀个售票排队的例⼦来解释公平锁:
售票窗⾥的售票员负责卖票,相当于AQS中的state变量,⼀个购票⼈⼀次可以买n张票,出队了还需要买票就需要重复排队,新购票⼈来买票时看当前售票窗有没有⼈正在买票,没有就可以直接买票,有就需要排队,排队就相当于AQS中的FIFO队列。就样的过程就是公平锁实现的思想,票是资源由售票员进⾏管理与售卖,买票需要有秩序,不能插队,让所有买票的⼈都是公平的,谁先来谁先买。
backordered⾮公平锁
线程获取锁时以不公平的⽅式进⾏,公平锁获取锁的⽅式是进⼊队列排队取锁,⽽⾮公平锁与公平锁在的区别只要就在排队上,⾮公平锁可以在队列的第⼀位的位置进⾏⼀次插队,插队成功就可以先获取锁,没有插队成功就需要像公平锁⼀样进⾏排队等待获取锁。⾮公平锁可能什么出现饥饿效应,队列中等待的线程与新来获取锁的线程在锁获取上是不公平的,有插队这⼀⾏为,正是因为有这⼀⾏为就有可能导致队列中等待的线程等待时间过长⽽出现⼀直未获取锁。虽然⾮公平锁有饥饿效应,但它相对于公平锁在获取锁的性能上更优,不会像公平锁⼀样每次都需要通知队列中的等待者去获取锁。下⾯还是通过图例来看看⾮公平锁的过程:
德威国际学校上图中还是使⽤购票这个例⼦,但排队的规则不⼀样了,现在可以进⾏⼀次插队,插队成功可以优先购票。
场景⼀:
新购票⼈4去购票,购票⼈刚好完成了购票并离开窗⼝,新购票⼈4进⾏插队,插队成功了,进⾏购票操作,购票完成后离开售票窗。
场景⼆:
新购票⼈4去购票,购票⼈刚好完成了购票并离开窗⼝,队列中等待购票⼈1优先站在了售票窗前进⾏购票操作,新购票⼈4没有插队成功,新购票⼈4只能去队列后⾯排队等待购票
乐知少儿英语>siso
因为有插队这⼀操作,排在最后的⼈等待时间会很长,在这炎炎烈⽇⾥⼼⾥有点怨⽓。
公平锁与⾮公平锁都有各⾃的优点与缺点,在Java中可以使⽤ReentrantLock类来使⽤公平锁与⾮公平锁
ReentrantLock 使⽤
//创建⼀个⾮公平锁
ReentrantLock nonfair=new ReentrantLock(fal);
try{
//获取锁
Lock()){
System.out.println("获取⾮公平锁成功!");
}
}finally{
//释放锁
nonfair.unlock();
执行力的重要性
}
//创建⼀个公平锁
古历ReentrantLock fair=new ReentrantLock(true);
try{
//获取锁
Lock()){
System.out.println("获取公平锁成功!");
}
}finally{
//释放锁
fair.unlock();
}英语三级考试成绩
ReentrantLock内部类Sync继承⾃AbstractQueuedSynchronizer类(AQS),实现了锁的基本功能,并使⽤FairSync内部类实现公平锁,使⽤NonfairSync实现⾮公平锁,这两个内部类都继承⾃Sync。
ReentrantLock 解决饥饿效应
使⽤⾮公平锁时可能会出现饥饿效应,这会导致某些线程⼀直不能获取到锁来进⾏后续任务的执⾏,这是⽣产环境不想看到的问题,如何解决?
饥饿效应产⽣的根本原因是线程在获取锁时在排队,⽽⾮公平锁使⽤了插队的⽅式来减少唤醒线程的CPU开销,插队导致后⾯的线程⼀直等待。这是饱效应的根本原因,根治的⽅法是让等待时间过长的线程有重新获取锁的机会,可以给每⼀个等待的线程⼀个超时时间,超过某⼀时间后可以重新获取⼀次锁,线程在获取锁的过程中加⼀个带有超时时间、⾃旋间隔的⾃旋逻辑。
ReentrantLock nonfair=new ReentrantLock(fal);
try{
Lock(1,TimeUnit.SECONDS)){
if(nonfair.isLocked()){
System.out.println("获取公平锁成功!");
}
}
}catch(InterruptedException e){
nonfair.unlock();
}finally{
nonfair.unlock();
}
往期推荐: