2026/4/7 17:14:13
网站建设
项目流程
自己做商品网站怎么做,展览网站制作,php网站服务器搭建,品牌建设三年行动方案目录一、AQS抽象类1.自定义AQS2.如何实现多个线程按序执行3.独占锁3.1 acquire()方法#xff08;ReentrantLock源码为例#xff09;3.2 release()方法#xff08;ReentrantLock源码为例#xff09;4.共享锁4.1 acquireShared()方法#xff08;Semaphore源码为例#xff0…目录一、AQS抽象类1.自定义AQS2.如何实现多个线程按序执行3.独占锁3.1 acquire()方法ReentrantLock源码为例3.2 release()方法ReentrantLock源码为例4.共享锁4.1 acquireShared()方法Semaphore源码为例4.2 releaseShared()方法Semaphore源码为例二、Condition一、AQS抽象类CLH锁获取锁失败时仍然会自旋性能差。AQS维护双向队列记录等待的线程head指针持有锁的线程、tail指针指向队列尾部用于插入新的线程、state字段用来记录锁的使用情况具体如何使用由实现类自定义ReentrantLock: state表示锁的重入次数、Semaphore: state表示可用许可数量、CountDownLatch: state表示剩余计数。线程首先CAS修改tail指针加入队列然后分别CAS修改自己的前向指针和前结点的后向指针。与CAS不同的是将获取锁失败的自旋操作替换为主动调用LockSupport.park()放弃CPU进入WATTING状态head结点在释放锁后根据后向指针调用LockSupport.unpark()唤醒后继节点后继结点获取锁后将自己的前向指针置空便于垃圾回收器回收然后开始获取任务后向指针由后继结点负责连接。上述结点和指针都要加volatile确保可见性。1.自定义AQSAQS是一个抽象类定义了资源获取和释放的通用框架双向队列statehead、tail等待唤醒机制而具体的资源获取逻辑需要重写模板方法来实现。需要定义的资源获取逻辑重写的模版方法//独占方式。尝试获取资源成功则返回true失败则返回false。protectedbooleantryAcquire(int)//独占方式。尝试释放资源成功则返回true失败则返回false。protectedbooleantryRelease(int)//共享方式。尝试获取资源。负数表示失败0表示成功但没有剩余可用资源正数表示成功且有剩余资源。protectedinttryAcquireShared(int)//共享方式。尝试释放资源成功则返回true失败则返回false。protectedbooleantryReleaseShared(int)//该线程是否正在独占资源。只有用到condition才需要去实现它。protectedbooleanisHeldExclusively()自定义AQS感觉可以实现多个线程有序执行的业务每个线程持有不同的id号所有线程CAS自旋volatile state仅当state为时获取锁每个线程执行完修改state为下一个要执行的线程的state然后释放锁。不过上述也没有用AQS现在还想不出来怎么用AQS实现主要是对这个抽象类的代码太陌生了。2.如何实现多个线程按序执行前两天看的面试题今天突然就有思路了。CAS实现多个线程有序执行的业务每个线程持有不同的id号所有线程CAS自旋volatile state仅当state为时获取锁每个线程执行完修改state为下一个要执行的线程的state然后释放锁。AQS因为CAS自旋太占CPU将所有线程乱序入队head→T1→T3→T2首先对首T1获取锁执行完唤醒T3但是T3CAS发现state跟自己序号不一样所以实例化一个新的结点将自己加入队尾释放锁唤醒T2T2执行完唤醒T3。3.独占锁ReentrantLock就是AQS的独占锁实现类定义state字段含义为锁的重入次数仅当state0时表示当前没有线程持有锁state0表示锁被占用同时基于state实现了可重入机制当state--至0时释放锁。3.1 acquire()方法ReentrantLock源码为例AQS本身不提供公平模式和非公平模式的实现而是由实现类在模版方法中自行编写需要的模式ReentrantLock实现了两种模式下面介绍非公平模式非公平模式当一个线程尝试获取锁时它会直接尝试获取而不管等待队列中是否有其他线程在等待。如果获取失败它才会加入等待队列。公平模式当一个线程尝试获取锁或信号量时它会先检查等待队列中是否有其他线程在等待如果有那么它就会直接进入等待队列而不是尝试获取。该方法用于获取资源会调用自定义的模版方法tryAcquire()首先获取state判断state0那么表示锁未被使用那么尝试CAS修改statestate1获取锁仅非公平模式下执行公平模式下直接进去等待队列。addWaiter()获取锁失败后将当前线程封装为结点CAS更新尾指针tail加入双向队列中。如果有前驱结点还需要CAS修改前驱结点的next指针指向当前结点指定前驱结点释放锁后要唤醒哪个结点。acquireQueued()当前线程加入队列之后如果发现head指向当前节点说明当前线程是队列中第一个等待的节点于是调用tryAcquire()尝试获取锁。如果尝试获取锁失败或不是第一个节点当前结点调用LockSupport.park()进入等待状态等待被唤醒。publicfinalvoidacquire(intarg){if(!tryAcquire(arg)acquireQueued(addWaiter(Node.EXCLUSIVE),arg))selfInterrupt();}protectedbooleantryAcquire(intacquires){finalThreadcurrentThread.currentThread();// 1、获取 AQS 中的 state 状态intcgetState();// 2、如果 state 为 0证明锁没有被其他线程占用if(c0){// 2.1、通过 CAS 对 state 进行更新if(compareAndSetState(0,acquires)){// 2.2、如果 CAS 更新成功就将锁的持有者设置为当前线程setExclusiveOwnerThread(current);returntrue;}}// 3、如果当前线程和锁的持有线程相同说明发生了「锁的重入」elseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)// overflowthrownewError(Maximum lock count exceeded);// 3.1、将锁的重入次数加 1setState(nextc);returntrue;}// 4、如果锁被其他线程占用就返回 false表示获取锁失败returnfalse;}privateNodeaddWaiter(Nodemode){// 1、将当前线程封装为 Node 节点。NodenodenewNode(Thread.currentThread(),mode);Nodepredtail;// 2、如果 pred null则证明 tail 节点已经被初始化直接将 Node 节点加入队列即可。if(pred!null){node.prevpred;// 2.1、通过 CAS 控制并发安全。if(compareAndSetTail(pred,node)){pred.nextnode;returnnode;}}// 3、初始化队列并将新创建的 Node 节点加入队列。enq(node);returnnode;}finalbooleanacquireQueued(finalNodenode,intarg){booleanfailedtrue;try{booleaninterruptedfalse;for(;;){// 1、尝试获取锁。finalNodepnode.predecessor();if(pheadtryAcquire(arg)){setHead(node);p.nextnull;// help GCfailedfalse;returninterrupted;}// 2、判断线程是否可以阻塞如果可以则阻塞当前线程。if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())interruptedtrue;}}finally{// 3、如果获取锁失败就会取消获取锁将节点状态更新为 CANCELLED。if(failed)cancelAcquire(node);}}3.2 release()方法ReentrantLock源码为例该方法用于释放资源会调用自定义的模版方法tryRelease()CAS更新state0释放锁修改持有锁的线程为null。unparkSuccessor()调用LockSupport.unpark()唤醒当前结点的后向指针指向的结点。publicfinalbooleanrelease(intarg){// 1、尝试释放锁if(tryRelease(arg)){Nodehhead;// 2、唤醒后继节点if(h!nullh.waitStatus!0)unparkSuccessor(h);returntrue;}returnfalse;}protectedfinalbooleantryRelease(intreleases){intcgetState()-releases;// 1、判断持有锁的线程是否为当前线程if(Thread.currentThread()!getExclusiveOwnerThread())thrownewIllegalMonitorStateException();booleanfreefalse;// 2、如果 state 为 0则表明当前线程已经没有重入次数。因此将 free 更新为 true表明该线程会释放锁。if(c0){freetrue;// 3、更新持有资源的线程为 nullsetExclusiveOwnerThread(null);}// 4、更新 state 值setState(c);returnfree;}// 这里的入参 node 为队列的头节点虚拟头节点privatevoidunparkSuccessor(Nodenode){intwsnode.waitStatus;// 1、将头节点的状态进行清除为后续的唤醒做准备。if(ws0)compareAndSetWaitStatus(node,ws,0);Nodesnode.next;// 2、如果后继节点异常则需要从 tail 向前遍历找到正常状态的节点进行唤醒。if(snull||s.waitStatus0){snull;for(Nodettail;t!nullt!node;tt.prev)if(t.waitStatus0)st;}if(s!null)// 3、唤醒后继节点LockSupport.unpark(s.thread);}4.共享锁Semaphore就是AQS的共享锁实现类可实现多线程同时持有锁定义state字段含义为锁的剩余容量仅当state0时表示当前锁已没有余量state0表示锁被占用但仍有余量可申请。4.1 acquireShared()方法Semaphore源码为例该方法用于获取资源会调用自定义的模版方法默认使用自定义的非公平模式tryAcquireShared()首先获取state判断state0那么表示锁还有剩余那么尝试CAS修改statestate-1获取锁仅非公平模式下执行公平模式下直接进去等待队列。doAcquireShared()获取锁失败后将当前线程封装为结点CAS更新尾指针tail加入双向队列中。如果有前驱结点还需要CAS修改前驱结点的next指针指向当前结点指定前驱结点释放锁后要唤醒哪个结点。如果head指向当前节点说明当前线程是队列中第一个等待的节点于是调用tryAcquire()尝试获取锁如果锁仍有余量那么同时唤醒后继结点。如果尝试获取锁失败或不是第一个节点当前结点调用LockSupport.park()进入等待状态等待资源有余量被唤醒。publicfinalvoidacquireShared(intarg){if(tryAcquireShared(arg)0)doAcquireShared(arg);}finalinttryAcquireShared(intacquires){for(;;){// 1、获取可用资源数量。intavailablegetState();// 2、计算剩余资源数量。intremainingavailable-acquires;// 3、如果剩余资源数量 0则说明资源不足直接返回如果 CAS 更新 state 成功则说明当前线程获取到了共享资源直接返回。if(remaining0||compareAndSetState(available,remaining))returnremaining;}}privatevoiddoAcquireShared(intarg){// 1、将当前线程加入到队列中等待。finalNodenodeaddWaiter(Node.SHARED);booleanfailedtrue;try{booleaninterruptedfalse;for(;;){finalNodepnode.predecessor();if(phead){// 2、如果当前线程是等待队列的第一个节点则尝试获取资源。intrtryAcquireShared(arg);if(r0){// 3、将当前线程节点移出等待队列并唤醒后续线程节点。setHeadAndPropagate(node,r);p.nextnull;// help GCif(interrupted)selfInterrupt();failedfalse;return;}}if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())interruptedtrue;}}finally{// 3、如果获取资源失败就会取消获取资源将节点状态更新为 CANCELLED。if(failed)cancelAcquire(node);}}4.2 releaseShared()方法Semaphore源码为例该方法用于释放资源会调用自定义的模版方法tryReleaseShared()CAS更新state释放锁。doReleaseShared()调用LockSupport.unpark()唤醒当前结点的后向指针指向的结点。publicfinalbooleanreleaseShared(intarg){if(tryReleaseShared(arg)){doReleaseShared();returntrue;}returnfalse;}protectedfinalbooleantryReleaseShared(intreleases){for(;;){intcurrentgetState();intnextcurrentreleases;if(nextcurrent)// overflowthrownewError(Maximum permit count exceeded);if(compareAndSetState(current,next))returntrue;}}二、ConditionObject类的wait()/notify()依赖于synchronized锁实现了线程间的等待和通知机制必须在synchronized代码块内调用且调用notify()会唤醒全部wait()在Object对象上的线程。Condition依赖ReentrantLock锁底层实现的是park/unpark机制它的优点是对于每个ReentrantLock对象可以设置多个Condition线程可以根据任务要求Condition.await()在ReentrantLock对象不同的Condition上唤醒线程时调用对应Condition.signal()实现部分唤醒。await()进入Condition等待队列等待被唤醒线程会唤醒后先获取ReentrantLock然后执行后的代码。signal()将Condition等待队列中的线程全部唤醒。publicclassReentrantLockDemo{staticReentrantLocklocknewReentrantLock();staticintstate0;publicstaticvoidmain(String[]args){// 一个锁对象可以创建多个ConditionConditionnotEmptylock.newCondition();ConditionnotFulllock.newCondition();newThread(()-{while(true){lock.lock();try{// await会自动释放锁线程被唤醒并执行后续代码前会先获取锁if(state0)notEmpty.await();state--;System.out.println(消费一次);notFull.signal();}catch(Exceptione){e.printStackTrace();}finally{lock.unlock();}}}).start();newThread(()-{while(true){lock.lock();try{// await会自动释放锁线程被唤醒并执行后续代码前会先获取锁if(state1)notFull.await();state;System.out.println(生产一次);notEmpty.signal();}catch(Exceptione){e.printStackTrace();}finally{lock.unlock();}}}).start();}}