当前位置: 首页 > 后端技术 > Java

JUC的四种重入锁,公平锁和非公平锁

时间:2023-04-02 09:08:26 Java

上一篇文章写了独占锁和共享锁,这次说说重入锁。可重入是指任何线程在获取锁后都可以再次获取锁而不会被锁阻塞。基于这个定义,会出现两个问题:线程再次获取锁。锁需要识别获取锁的线程是否为当前占用锁的线程,如果是则再次获取成功。这会涉及到锁获取的公平性和不公平性,下面会讲到。最后释放锁。该线程重复获取锁N次,然后其他线程释放锁N次后才能获取锁。我们知道synchronized支持隐式重入。如果synchronized修饰的方法中存在递归,线程不会阻塞自己。ReentrantLock通过结合自定义同步器来实现锁的获取和释放,并以不公平(默认)方式实现。锁重入次数保存在volatile修饰的state变量中。需要说明的是,重入锁不是共享锁,所以可以直接用状态来记录,后面说到的读写锁中的读锁是共享的,所以使用threadlocal记录读锁获取次数。重要的内部类ReentrantLock中有3个重要的内部类,分别是Sync、NonfairSync和FairSync:Sync是后两者的父类,继承自AbstractQueuedSynchronizer。NonfairSync和FairSync都继承自Sync。NonfairSync主要用来实现非公平锁,FairSync主要用来实现公平锁。重要属性privatefinalSync同步;在构造函数中进行初始化,通过构造函数参数决定使用公平锁还是非公平锁。重要的构造函数//unfairlockpublicReentrantLock(){sync=newNonfairSync();}//fairlockpublicReentrantLock(booleanfair){sync=fair?newFairSync():newNonfairSync();}fairand公平锁和非公平锁的区别在于获取锁。如果一个锁是公平的,那么获取锁的顺序应该符合请求的绝对时间顺序,即先进先出。下面对比一下获取公平锁和非公平锁的代码:staticfinalclassNonfairSyncextendsSync{/***执行锁。尝试立即插入,备份到正常*失败时获取。*非公平锁先由CAS设置,如果成功,就会获取成功,这是与公平锁的区别之一*如果不成功,就会加入同步队列*/finalvoidlock(){如果(compareAndSetState(0,1))setExclusiveOwnerThread(Thread.currentThread());否则获取(1);}protectedfinalbooleantryAcquire(intacquires){returnnonfairTryAcquire(acquires);}}//获取非公平锁finalbooleannonfairTryAcquire(intacquires){finalThreadcurrent=Thread.currentThread();//同一个线程获取锁的次数//如果同一个线程没有释放,再次获取成功,则累计次数intc=getState();if(c==0){//没有线程获得锁if(compareAndSetState(0,acquires)){//设置当前线程为独占锁线程setExclusiveOwnerThread(current);returntrue;//获取成功}}//判断当前线程是否为获取锁的线程elseif(current==getExclusiveOwnerThread()){intnextc=c+acquires;//获取次数累计if(nextc<0)//溢出thrownewError("超过最大锁计数");设置状态(下一步);returntrue;//获取成功}returnfalse;}//公平获取锁staticfinalclassFairSyncextendsSync{finalvoidlock(){acquire(1);}/***tryAcquire的公平版本。不要授予访问权限,除非*递归调用或没有服务员或是第一个。*/protectedfinalbooleantryAcquire(intacquires){finalThreadcurrent=Thread.当前线程();intc=getState();if(c==0){//和unfair唯一的区别是加入同步队列中的当前节点//是否有前驱节点如果有线程比当前线程更早请求获取锁,//则需要等待上一个线程获取锁并释放后,再继续获取锁。if(!hasQueuedPredecessors()&&compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);返回真;}}elseif(current==getExclusiveOwnerThread()){intnextc=c+acquires;if(nextc<0)thrownewError("超过最大锁计数");设置状态(下一步);返回真;}返回假;}}接着看下锁的释放:protectedfinalbooleantryRelease(intreleases){intc=getState()-releases;if(Thread.currentThread()!=getExclusiveOwnerThread())thrownewIllegalMonitorStateException();布尔自由=假;如果(c==0){免费=真;setExclusiveOwnerThread(null);}设置状态(c);免费退货;}如果已经获取了N次锁,前(N-1)次释放时返回false,只有同步状态完全释放后才返回trueCAS设置状态。如果设置成功,则表示获取锁成功。如果失败,则将其添加到同步队列中。在此前提下,刚刚释放锁的线程本次获得锁的概率非常高。公平锁获取过程中会判断当前加入同步队列的节点是否有前驱节点,这是与非公平锁获取的区别之一。公平锁遵循FIFO以避免饥饿。非公平锁可以减少线程上下文切换,比公平锁有更高的吞吐量。参考文章:①ReentrantLock公平锁和非公平锁Java可重入锁ReentrantLock原理分析