Java从版本5开始,在java。util。concurrent。locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可以抛弃synchronized关键字了。 在这些锁实现之前,如果我们想实现锁的功能,只能通过synchronized关键字,例子如下:privatestaticvolatileintvalue;publicstaticvoidmain(){RunnablerunnewRunnable(){Overridepublicvoidrun(){for(inti0;i1000;i){increaseBySync();}}};Threadt1newThread(run);Threadt2newThread(run);t1。start();t2。start();while(Thread。activeCount()1){Thread。yield();}System。out。println(value);}privatestaticsynchronizedintincreaseBySync(){returnvalue;} 有了ReentrantLock之后,我们可以这样写:privatestaticvolatileintvalue;privatestaticLocklocknewReentrantLock();publicstaticvoidmain(String〔〕args){testIncreaseWithLock();}publicstaticvoidmain(){RunnablerunnewRunnable(){Overridepublicvoidrun(){try{lock。lock();for(inti0;i1000;i){value;}}finally{lock。unlock();}}};Threadt1newThread(run);Threadt2newThread(run);t1。start();t2。start();while(Thread。activeCount()1){Thread。yield();}System。out。println(value);} 以上两段代码都可以实现value值同步自增1,并且value都能得到正确的值2000。下面我们就来分析下ReentrantLock有哪些功能以及它底层的原理。可重入锁 从名字我们就可以看出ReentrantLock是可重入锁。可重入锁是指当某个线程已经获得了该锁时,再次调用lock()方法可以再次立即获得该锁。 举个例子,当某个线程在执行methodA()时,假设已经获得了锁,这是当它执行到methodB()时可以立即获得methodB里面的锁,因为两个方法是调用的同一把锁。privatestaticLocklocknewReentrantLock();publicstaticvoidmethodA(){try{lock。lock();dosomethingmethodB();}finally{lock。unlock();}}publicstaticvoidmethodB(){try{lock。lock();dosomthing}finally{lock。unlock();}} synchronized也是可重入锁,有兴趣的同学可以自己写个例子测试下。公平锁 通过源码我们可以看到ReentrantLock支持公平锁,并且默认是非公平锁。ReentrantLock源码publicReentrantLock(){syncnewNonfairSync();}ReentrantLock源码publicReentrantLock(booleanfair){syncfair?newFairSync():newNonfairSync();} 下面是非公平锁和公平锁的lock()方法实现,从两个lock()方法我们可以看到,它们的不同点是在调用非公平锁的lock()方法时,当前线程会尝试去获取锁,如果获取失败了则调用acquire(1)方法入队列等待;而调用公平锁的lock()方法当前线程会直接入队列等待(acquire方法涉及到AQS下面会讲到)。ReentrantLock源码,非公平锁finalvoidlock(){if(compareAndSetState(0,1))setExclusiveOwnerThread(Thread。currentThread());elseacquire(1);}ReentrantLock源码,公平锁finalvoidlock(){acquire(1);} 而synchronized是一个非公平锁。超时机制 ReentrantLock还提供了超时机制,当调用tryLock()方法,当前线程如果获取锁失败会立刻返回;而当调用带参tryLock()方法时,当前线程如果在设置的timeout时间内未获得锁,也会立刻返回。而这些功能背后主要是依赖AQS实现的。ReentrantLock源码publicbooleantryLock(){returnsync。nonfairTryAcquire(1);}ReentrantLock源码publicbooleantryLock(longtimeout,TimeUnitunit)throwsInterruptedException{returnsync。tryAcquireNanos(1,unit。toNanos(timeout));} synchronized没有这个功能。可被中断 ReentrantLock有一个lockInterruptibly()方法,它会最终调用AQS的两个方法: AQS方法一中如果当前线程被中断则抛出InterruptedException,否则尝试去获取锁,获取成功则返回,获取失败则调用aqs方法二doAcquireInterruptibly()。 AQS方法二中在for循环线程自旋中也会判断当前线程是否被标记为中断,如果是也会抛出InterruptedException。 doAcquireInterruptibly()的细节我们在下面讲解AQS的时候会重点介绍,它和doAcquire()方法很类似,唯一区别是会抛出InterruptedException。lock方法publicvoidlockInterruptibly()throwsInterruptedException{sync。acquireInterruptibly(1);}aqs方法一publicfinalvoidacquireInterruptibly(intarg)throwsInterruptedException{if(Thread。interrupted())thrownewInterruptedException();if(!tryAcquire(arg))doAcquireInterruptibly(arg);}aqs方法二privatevoiddoAcquireInterruptibly(intarg)throwsInterruptedException{finalNodenodeaddWaiter(Node。EXCLUSIVE);booleanfailedtrue;try{for(;;){finalNodepnode。predecessor();if(pheadtryAcquire(arg)){setHead(node);p。nextnull;helpGCfailedfalse;return;}if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())thrownewInterruptedException();}}finally{if(failed)cancelAcquire(node);}}AQS(AbstractQueueSynchronizer) AQS是一个基于队列的同步器,它是一个抽象类,主要提供了多线程获取锁时候的排队等待和激活机制,ReentrantLock内部有两个基于AQS实现的子类,分别针对公平锁和非公平锁做了支持。 下面我们以公平锁为例,讲解下ReentrantLock是如何依赖AQS实现其功能的。获得锁涉及到的主要源代码和解释如下:AQS源码,公平锁的lock()方法会直接调用该方法这里当前如果获取失败会调用acquireQueued方法addWaiter方法主要是将当前线程加入AQS内部队列的尾部publicfinalvoidacquire(intarg){if(!tryAcquire(arg)acquireQueued(addWaiter(Node。EXCLUSIVE),arg))selfInterrupt();}ReentrantLock中实现公平锁的AQS子类的方法protectedfinalbooleantryAcquire(intacquires){finalThreadcurrentThread。currentThread();intcgetState();c0表示当前AQS为初始状态,可以尝试获取锁,如获取成功则返回trueif(c0){if(!hasQueuedPredecessors()compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}只有当前线程是已经获取了该锁的线程才能再次获取锁(可重入锁)并返回trueelseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)thrownewError(Maximumlockcountexceeded);setState(nextc);returntrue;}返回false获取失败returnfalse;}AQS源码finalbooleanacquireQueued(finalNodenode,intarg){booleanfailedtrue;try{booleaninterruptedfalse;for(;;){finalNodepnode。predecessor();这里如果当前线程对应的队列里的Node的前置Node是head,则尝试获取锁,并成功返回if(pheadtryAcquire(arg)){setHead(node);p。nextnull;helpGCfailedfalse;returninterrupted;}shouldParkAfterFailedAcquire方法标记当前线程Node的前置Node的waitStatus为SIGNAL,意思是当你从队列移除时记得要唤醒我哦parkAndCheckInterrupt方法会让当前线程挂起,停止自旋,免得白白浪费CPU资源if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())interruptedtrue;}}finally{if(failed)cancelAcquire(node);}} 假设现在有线程A、B、C同时去获取同一把锁,大概的流程是这样的,这里假设线程A获得锁并成功返回,线程B和C则依次调用上面的方法,进入AQS的等待队列并最终被挂起。 这时线程A做完自己该做的事情了,它在finally块中调用了unlock方法,这时我们再看下相关的源代码。AQS源码,当前线程在释放锁的同时,会判断它所在Node的waitStatus有没有被它的后继结点标记,如果被标记了,那就唤醒后继结点对应的线程publicfinalbooleanrelease(intarg){if(tryRelease(arg)){Nodehhead;if(h!nullh。waitStatus!0)unparkSuccessor(h);returntrue;}returnfalse;}AQS源码,主要关注最下面LockSupport。unpark(s。thread),唤醒后继结点线程privatevoidunparkSuccessor(Nodenode){Ifstatusisnegative(i。e。,possiblyneedingsignal)trytoclearinanticipationofsignalling。ItisOKifthisfailsorifstatusischangedbywaitingthread。intwsnode。waitStatus;if(ws0)compareAndSetWaitStatus(node,ws,0);Threadtounparkisheldinsuccessor,whichisnormallyjustthenextnode。Butifcancelledorapparentlynull,traversebackwardsfromtailtofindtheactualnoncancelledsuccessor。Nodesnode。next;if(snulls。waitStatus0){snull;for(Nodettail;t!nullt!node;tt。prev)if(t。waitStatus0)st;}if(s!null)LockSupport。unpark(s。thread);} 线程A释放锁时,会唤醒head结点的后继结点也就是线程B,此时线程B就可以继续for循环里面自旋并成功获得锁了。unsafe相关 之前介绍AtomicInteger的时候提到Unsafe对象,AtomicInteger用到了Unsafe对象的CAS功能(底层是cpu提供的功能)。 ReentrantLock除了用到了CAS之外,还用到了Unsafe的pack和unpack两个方法(LockSupport当中),除了性能更好之外,它可以精确的唤醒某一个线程。Demo代码位置 srcmainjavanetweichitechjucReentrantLockTest。java小西学编程javalearningGitee。com相关文章 JAVA并发之AtomicInteger原理分析