AbstractQueuedSynchronizer预热的示例分析
发表于:2024-11-17 作者:千家信息网编辑
千家信息网最后更新 2024年11月17日,这篇文章给大家分享的是有关AbstractQueuedSynchronizer预热的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。核心方法预热 // 我不确定
千家信息网最后更新 2024年11月17日AbstractQueuedSynchronizer预热的示例分析
这篇文章给大家分享的是有关AbstractQueuedSynchronizer预热的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。
核心方法预热
// 我不确定有多少人卡在这里 // 我是这么理解的 某个对象在jvm当中 是用一块数据来描述对象的所有信息 // 那么问题来了 如果我要设置某个对象的字段 通常的方法 对象引用.setXXXField(xxx)这个是通常的方法 // 还有一种比较特别的 unsafe提供的 unsafe.objectFieldOffset获取某个字段的偏移量 可以理解为存储信息的地址 // 获得了偏移地址之后 就可以使用 unsafe.compareAndSwapObject来原子的设置某个对象的字段 // 就是说 绕过通用的流程 直接修改相关数据了 顺带而且是原子性的 // 可以理解为玩游戏用外挂直接修改内存这种场景 headOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("head")); unsafe.compareAndSwapObject(this, headOffset, expect, update); tailOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("tail")); unsafe.compareAndSwapObject(this, tailOffset, expect, update); /** * 独占式获取同步状态,忽略线程的打断。 * 获取同步状态的逻辑是由重写的模板方法tryAcquire来实现的。 * 如果获取同步状态成功,则方法就直接返回。 * 否则,线程就会入队,一直会处于阻塞或者自旋,直到重复尝试tryAcquire成功。 * 该方法就是接口Lock#lock的实现。 * (从方法的介绍上面理解,就是说,这个接口直接的效果就是,获取同步成功,线程就从这个方法继续执行下去,如果不成功; * 那么内部会经过一系列复杂的逻辑计算,直接体现就是线程不会继续执行下去,就一直处于这个方法内部。不执行下去的原因是:线程可能处于自旋或者阻塞。) * @param arg 同步状态参数 透传进tryAcquire并且不响应终端或者其他情况(超时) * * 由两种判断逻辑 * 1. tryAcquire(arg) -> 返回 * 2. tryAcquire(arg) -> addWaiter(Node.EXECLUSIVE) -> acquireQueued(lastValue, arg) -> 返回并且可能会中断线程 * * addWaiter(Node node) 入队 * acquireQueued(final Node node, int arg) 自旋或者阻塞 * * 这个方法就是把整个流程已经写死了,必定会经过这么几个步骤。 * 唯一可以影响该方法中的流程,只能是模板方法tryAcquire,它的返回与否,导致流程的走向。 * 把自旋或者阻塞安排在if的条件语句中 会令人初步一看会感觉非常难受。(大神可以这么用,我们平时还是少用)。 */ public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 老老实实的我,一般会这么写 见笑见笑 public final void acquire(int arg) { // 尝试获取同步状态 if (tryAcquire(arg)) { return; } else { // 先入队 这里会有一个死循环 Node newNode = addWaiter(Node.EXCLUSIVE); // 再自旋获取同步状态 或者阻塞 这里也会有死循环 boolean shouldCurrentThreadInterrupted = acquireQueued(newNode, arg); // 再判断是否需要线程中断 if (shouldCurrentThreadInterrupted) { selfInterrupt(); } } } // 接下来看看这个模板方法的介绍 /** * 尝试独占式获取同步状态。 * 该方法需要查询对象当前状态,判断同步状态是否符合预期。 * (我的理解就是,需要自己实现自己的逻辑,判断自己所要实现的逻辑是否符合自己的预期。记住是独占模式) * * 该方法经常再线程执行同步时被调用。 * 如果方法返回失败,那么线程就应该入队了,即使线程还没做好入队准备。 * (这里的意思就是说,线程在竞争锁之前,最好做好充足的准备工作,也就是前置逻辑要执行完,比如各种初始化判断。加锁之后就应该是确确实实的逻辑操作了,最好不要加完锁之后,又去判断各种前置业务逻辑操作。这个就是我理解的大师所要阐述的最佳实践。) * 入队的线程只能等待别人释放之后唤醒。 * 一般前置方法就是为了实现Lock#tryLock这个。 * * 默认实现式UnsupportedOperationException异常。 * * @param arg 请求参数。 * 一般这个值是方法唯一的参数,或者保存于条件等待中。 * 所以不建议为这个值赋予更多其他含义。 * (我认为这里的意思是,这个值不要和业务中的某个条件或者流程挂钩,让值单纯的标识同步状态就好了。) * * @return true加锁成功。 * @throws IllegalMonitorStateException 如果获取同步时发现同步器处于一个不正确的状态时, * 那么就必须抛出这个异常,目的时为了同步器逻辑正确。 * (我的理解,同步器状态很重要,必须严肃对待,因为一旦某个过程状态不正确,后续的业务逻辑可能会发生各种不可知的结果,并且,debug起来非常麻烦,因为业务逻辑可能正确,原因是同步状态的出错。这种是很隐晦的。也就是说,一旦碰到IllegalMonitorStateException,个人认为最好中断运行,排错。即使开发者认为这个错误不重要。你都已经自己实现锁的逻辑了,任何一点小的逻辑失误,都会造成不可预估的结果。千里之堤毁于蚁穴啊。) * @throws UnsupportedOperationException 如果独占模式不支持抛异常 */ protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } // 入队操作 /** * 创建队列,并且把当前线程包装一下,指定某个节点模式,入队。 * * @param mode Node.EXCLUSIVE 独占, Node.SHARED 共享 * @return 新的节点 */ private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // 先尝试直接队尾添加 如果不行在进行完整的入队操作 Try the fast path of enq; backup to full enq on failure Node pred = tail; // 队尾有两种情况 // 1 null 表示队列还没有初始化 初始化在enq(node)中 // 2 != null 表示队列初始化了 那么尝试快速添加队尾这个操作 我认为就是优化操作了 // (老老实实的我,一般并不会这么写,因为我比较稳妥。) // (其实优化操作,理论上来说,可以不用的。) // compareAndSetTail()这个原子性的操作 防止并发 // 并发操作的特点就是,随时随地都可能发生几个线程同时执行,所以,并发点,尽量条件简单点,如果业务条件够复杂,一定要拆,而且要分优先级的。不然,动态变化的条件加上锁,噩梦。 if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { // 入队操作只需要建立一个尾链接就可以 pred.next = node; return node; // 注意 这里返回的是新的节点 } } enq(node); // 这里方法返回的是节点前置的节点 但是没有使用 在唤醒流程中会复用这个方法 return node; } // 完整的入队流程逻辑 /** * 入队操作,一定要先初始化队列。 * (死循环确保一定会入队成功,我对死循环的理解是,单线程不要用死循环,多线程可以适量的用,主线程不要用,非要用时情愿开个线程计算,等它计算结束再拿那个结果也可以。总结起来,能不用就不用,即使要用,千万别忘记了,自己在干什么。建议在自己精力最旺盛的时候,写带有死循环的逻辑。) * @param node 入队节点 * @return 返回前置节点 */ private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // 队列初始化 // 原子性的设置头 这里注意这个head节点 这个head指向的node是一个空的node,里面没有node的关键数据的 if (compareAndSetHead(new Node())) tail = head; } else { // 双向队列 尝试把当前节点的头设置为原本队尾那个 只要下面的cas队列设置好那就操作成功 不行再循环再来 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } /** * 设置队列首节点 (因为是双向,队首的前驱是null,这个null是为了释放节点的。) * 该方法仅仅只被同步器获取。 * null的目的是为了GC也为了不必要的信号释放遍历。 * * @param node 设置队首 */ private void setHead(Node node) { head = node; node.thread = null; node.prev = null; } // 自旋 /** * 独占不响应中断模式的线程获取同步方法。 * 条件等待也使用该方法。 * * @param node 节点 * @param arg 获取同步参数 * @return true 如果等待时线程被打断 */ final boolean acquireQueued(final Node node, int arg) { // 获取同步状态是否失败 // 默认标记值是成功的 boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 节点的前驱节点就是头节点 // 说明前面的节点,要么持有同步状态在进行业务逻辑操作,要么就已经释放锁了。这种情况下,获取同步器机会就很大。 // 再次尝试获取同步状态 if (p == head && tryAcquire(arg)) { // 这里已经说明当前节点已经获得了同步状态 也就是说当前线程也获得执行业务逻辑的机会了 // 设置头节点很有技巧 设置完之后 头已经是一个虚拟的节点了 setHead(node); p.next = null; // help GC failed = false; // 这里其实个人认为是不需要设置了 除了习惯原因 我不知道还有什么特别的意思?因为返回的时候是表示线程是否被打断了标记 return interrupted; } // 获取失败判断线程是否需要阻塞 // 阻塞之后又要检查线程是否需要中断 // if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; // 线程已经被打断 } } finally { if (failed) cancelAcquire(node); } } /** * 当一个节点获取同状态失败时,检查并且更新它的状态。 * 返回true,那么线程需要被阻塞。 * 在所有的获取同步循环中,这个是最重要的信号控制。 * 前置条件是前置节点确切的是节点的前置节点。 * * @param pred 带有状态的前驱节点 * @param node 节点 * @return true 线程被阻塞 */ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * 前驱节点已经处于等待其他线程释放同步状态而将它唤醒。 * 那么当前节点应该能够安全的被阻塞。 */ return true; if (ws > 0) { /* * 前驱节点已经是取消状态。 * 跳过前驱节点在尝试。 */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * 等待状态必须是0或者是传播状态(-3)。 * 仅需要一个信号,而并不需要阻塞。(应该是共享模式下的逻辑。) * 调用者需要重新确保当前线程在阻塞之前是否需要获取同步状态。 */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } /** * 阻塞当前线程。恢复后检测线程是否被中断了。 * * @return true} if interrupted */ private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
感谢各位的阅读!关于"AbstractQueuedSynchronizer预热的示例分析"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!
线程
节点
同步
状态
方法
逻辑
就是
阻塞
成功
条件
队列
尝试
循环
业务
流程
前驱
对象
模式
原子
参数
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
深圳扩斯网络技术有限公司
internet代理服务器路径
机顶盒谁提供的服务器
西安多媒体博物馆软件开发
非关系型数据库python
正在尝试链接其它服务器
300183网络安全龙头
怎样从数据库随机获取信息
商业系统数据库
软件开发过程与模型
将文件存入数据库java
信息产品软件开发任务书
云计算中的数据库是做什么的
荣昌区常规软件开发流程欢迎咨询
eth矿池服务器
ftp服务器要多大
小型网络技术厂家价格
学校网络安全防护工作开展
北京字节跳动网络技术
软件开发去哪个城市最好
网络安全有etf
工厂网络安全应急预案范本
美国网络技术最发达的地区
网络安全寒
数据库 网络通信
信息产品软件开发任务书
三个字网络技术公司名字
丽江网络安全学习
网络安全的专业有哪些
安全的存储服务器