ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java进阶篇之AtomicMarkableReference:引用还在,标记已经改变

Java进阶篇之AtomicMarkableReference:引用还在,标记已经改变 上一篇用AtomicStampedReference记录引用的变化轮次。继续往下看有些业务只需要表达两个状态节点还有效或者节点已被标记删除。把一个布尔值与引用一起观察就能让提交条件带上这个状态。今天来看AtomicMarkableReference。我们让一个线程先读取未标记的节点另一个线程将它标记再检查旧提交还能不能成功。随后把标记恢复观察布尔位与版本戳的差别。本文适用于Java21示例在JDK21.0.8实际编译运行。这里的节点与删除流程是教学模型未实现完整并发链表也没有性能测试数据。一、一个引用配一个布尔标记AtomicMarkableReference维护引用与boolean标记。应用给标记赋予含义例如false代表有效、true代表逻辑删除。类本身不会自动解释、翻转或清理这个标记。成对读取与条件更新可参考Java21官方API。初始观察(A, false) 标记之后(A, true)同一个A仍在共享位置里状态已经变化。持有旧观察的线程如果只比较引用就会漏掉这个标记同时比较引用与标记才能表达“这个节点仍然有效”的提交条件。二、逻辑删除怎样进入提交条件调用compareAndSet(expectedReference, newReference, expectedMark, newMark)时期望引用按引用身份比较也就是标记也必须匹配满足后一起更新。对象字段相同或equals()相等不能代替相同引用。例如下面的操作保留A引用只将false改为truebooleandeletedstate.compareAndSet(a,a,false,true);对于已经保存(A, false)的读取者后续提交会因当前标记为true而失败。这表示应用约定的有效性条件改变并不表示A对象已经被回收。图中的标牌对应状态位。节点留在原位时也能被标记将它从数据结构中解除连接需要另一套完整协议。三、一次取出同一对状态需要同时使用引用与标记时调用get(boolean[] markHolder)。返回值给出引用数组第一个位置接收对应标记数组至少有一个元素。boolean[]holdernewboolean[1];Nodeoldstate.get(holder);booleanoldMarkholder[0];booleanchangedstate.compareAndSet(old,next,oldMark,false);分开调用getReference()与isMarked()时两次读取之间可能发生更新。局部holder与成对读取让本次提交的观察来源更清楚。CAS失败后应重新检查新状态再计算下一步。类还提供attemptMark(expectedReference, newMark)只按期望引用尝试改变标记没有expectedMark参数。需要明确检验旧标记时选用带两个标记参数的CAS更直接。attemptMark文档也提醒可能发生伪失败不宜把一次false直接解释为引用一定变化。四、完整示例标记变化拒绝旧提交两个CountDownLatch确定先读取、后标记、再提交的顺序。示例后半段主动恢复标记用来观察状态重复实际逻辑删除协议可以禁止这个恢复动作。importjava.util.concurrent.CountDownLatch;importjava.util.concurrent.atomic.AtomicMarkableReference;publicclassMarkableReferenceDemo{recordNode(Stringname){}publicstaticvoidmain(String[]args)throwsException{NodeanewNode(A);NodebnewNode(B);AtomicMarkableReferenceNodestatenewAtomicMarkableReference(a,false);CountDownLatchobservednewCountDownLatch(1);CountDownLatchmarkednewCountDownLatch(1);ThreadreaderThread.ofPlatform().start(()-{boolean[]holdernewboolean[1];Nodeoldstate.get(holder);booleanoldMarkholder[0];System.out.println(snapshotold.name()/oldMark);observed.countDown();try{marked.await();}catch(InterruptedExceptione){Thread.currentThread().interrupt();return;}System.out.println(staleCasstate.compareAndSet(old,b,oldMark,false));});observed.await();System.out.println(logicalDeletestate.compareAndSet(a,a,false,true));marked.countDown();reader.join();boolean[]holdernewboolean[1];Nodecurrentstate.get(holder);System.out.println(afterMarkcurrent.name()/holder[0]);System.out.println(sameValueOtherObjectstate.compareAndSet(newNode(A),b,true,false));// Demonstrate a reset; a production deletion protocol may forbid it.System.out.println(resetMarkstate.compareAndSet(a,a,true,false));System.out.println(oldPairMatchesAgainstate.compareAndSet(a,b,false,false));currentstate.get(holder);System.out.println(finalcurrent.name()/holder[0]);}}保存为MarkableReferenceDemo.java后执行javac-encodingUTF-8 MarkableReferenceDemo.javajavaMarkableReferenceDemo本次实际输出snapshotA/false logicalDeletetrue staleCasfalse afterMarkA/true sameValueOtherObjectfalse resetMarktrue oldPairMatchesAgaintrue finalB/falsestaleCasfalse说明旧观察未通过当前标记的检查。sameValueOtherObjectfalse说明新建一个字段同为A的Node也无法代替原引用。最后oldPairMatchesAgaintrue则揭示布尔位的边界标记恢复后原来的那对值再次出现。五、布尔状态与版本轮次如何选择业务问题更适合表达的状态当前节点是否已标记引用与boolean标记是否仍是原来那一轮更新引用与应用维护的stamp多个业务字段必须一起满足规则完整状态、锁或事务等明确协议布尔位记录当前状态。false到true再回false只有两个取值无法保存每次变化的历史。不能据此宣称AtomicMarkableReference普遍解决所有ABA场景是否可用需要分析状态是否允许恢复以及旧观察的生命周期。逻辑删除需要配套算法。标记后谁负责跳过节点、何时解除连接、并发读写怎样协作都超出这一对状态的原子更新。本文的Node不包含链表指针不能直接复制成无锁链表实现。对象内部仍要有约束。引用与标记一起更新不会自动保护对象内的可变成员。这里使用只有String字段的record把示例关注点留在成对状态上。六、几个上手注意事项给false与true写清业务含义并让全部写入遵守同一约定。随意调用set()恢复状态可能重新放行早先的观察。CAS只决定这次状态提交是否成功邮件发送、文件写入等外部操作需要另外安排。将外部动作塞进反复尝试的循环可能造成多次执行。观察失败时先检查业务前提。一个已标记节点可能需要停止操作或重新寻找节点持续盲重试未必能推进业务。七、 思维导图AtomicMarkableReference状态表示引用与boolean标记成对读取get取得同一观察条件提交引用身份与旧标记匹配逻辑删除标记与解除连接分开设计适用边界布尔位不能记录变化轮次八、总结总结要点引用与标记成对使用。先取得同一观察再将旧引用与旧标记放进CAS条件标记变化就能拒绝旧提交。标记承载应用状态。逻辑删除是常见用途真正的解除连接与并发数据结构仍需要完整算法。布尔位有表达边界。状态恢复后旧值可以再次匹配关心变化轮次时应考虑版本信息与一致的写入协议。下一篇继续看AtomicIntegerArray理解数组中单个元素的原子更新以及它与整组数据一致性的边界。如果你觉得这篇文章对你有所帮助欢迎点赞、收藏、分享
返回列表