当前位置: 首页 > news >正文

Java并发编程的艺术(七)——Java中的锁(2)

队列同步器的实现分析

写在前边:队列同步器的实现分析包括同步队列、独占式同步状态获取与释放、共享式同步状态获取与释放、超时获取同步状态等。受限于篇幅,本篇先分析了同步队列、独占式同步状态获取与释放。同时,阅读本篇前,推荐先阅读Java中的锁(1)。

1. 同步队列

●同步器内部维护了一个同步队列(FIFO双向队列)来管理线程的排队工作。当前线程获取同步状态失败时,同步器会把当前线程以及相关信息构造成一个节点(Node)加入到同步队列的队尾,并阻塞当前线程。当同步状态被释放后,同步队列中首节点将被唤醒,且将参加到获取新一轮同步状态的竞争中去。

● 需要注意的是,同步队列中的首节点是获取同步状态成功的节点。首节点在释放同步状态后,会唤醒其后续节点,后续节点在获取同步状态成功后将自己设置为首节点。

2. 独占式同步状态获取与释放

●调用同步器的acquire(int arg)方法可独占式获取同步状态,该方法如下:

public final void acquire(long arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
复制代码
  • 1.tryAcquire(int arg)方法先尝试非阻塞获取同步状态,若成功,则该方法直接返回;否则执行第2、3步。

  • 2.addWaiter(Node mode)表示因获取同步状态失败将当前线程及相关信息构造成一个节点,并 将其加入到同步队列的队尾。

  • 3.acquireQueued(Node node,int arg)方法表示让该节点以自旋(死循环)的方式不断尝试获取同步状态。


●关于addWaiter(Node mode)方法的分析如下:

 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 快速尝试在同步队列的队尾添加node
        Node pred = tail;     //同步器拥有首节点(head)和尾节点(tail)
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
复制代码
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
复制代码
  • 1.compareAndSetTail(Node expect, Node update)保证node能被安全地添加到同步队列。若不这么做,可能在同一时刻有多个节点准备同时添加到同步队列的队尾,最终的结果是节点的数量有偏差且顺序也是混乱的。
  • 2.在调用enq(final Node node)方法以前,若节点入队成功,则不必调用enq(final Node node)方法程序就返回了;若入队失败,调用enq(final Node node)通过"死循环"的方式保证节点一定能入队成功。

●关于acquireQueued(Node node,int arg)方法的分析如下:

 final boolean acquireQueued(final Node node, long 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);
        }
    }
复制代码
  1. 1.acquireQueued(Node node,int arg)表示节点进入队列后以自旋的方式不断地尝试获取同步状态,获取成功则从自旋中退出。
  2. 2.只有前驱节点(prev)是首结点(head)的节点才能尝试获取同步状态,原因有三:
    • ●维护同步队列的FIFO原则。
    • ●同步队列中的首结点是获取同步状态成功的节点,而首节点在释放了同步状态后会唤醒后续的节点,后续节点被唤醒后需要检查自己的前驱节点是否为首节点。
    • ●被过早通知(过早通知是指前驱节点不是首节点的节点由于中断而被唤醒)的节点若成功获取到同步状态,会和"同步队列首节点是成功获取同步状态的节点"这一原则相违背。

●同步器释放同步状态的方法如下:

public final boolean release(long arg) {
       if (tryRelease(arg)) {
           Node h = head;
           if (h != null && h.waitStatus != 0)
               unparkSuccessor(h); //唤醒首节点的后续节点线程
           return true;
       }
       return false;
   }
复制代码

总结:在获取独占式同步状态时,同步器维护一个同步队列,获取同步状态失败的节点将加入到队列里,并通过自旋不断尝试获取同步状态。移出队列的条件是前驱节点为首节点且成功获取了同步状态。释放同步状态将唤醒首节点的后续节点。

转载于:https://juejin.im/post/5cfa1f3251882539c33e4f52

相关文章:

  • Django 之 orm操作
  • 单链表的逆转
  • 坑爹的 Java 可变参数,把我整得够惨。。
  • day25-2 random,os,sys模块
  • 文件传输协议介绍
  • 前端开发者必备的 Nginx 知识
  • WebSocket Client连接AspNetCore SignalR Json Hub
  • 精读vue-hooks
  • 扩展SpringMVC以支持更精准的数据绑定1
  • 如何安装部署秋色园QBlog站点
  • Myeclipse优化配置
  • 转换流、缓冲流、流的操作规律
  • TypeScript 学习总结 函数 接口 (二)
  • javaweb期末项目-stage3-项目测试和发布
  • RabbitMQ安装配置-01
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • 0x05 Python数据分析,Anaconda八斩刀
  • CODING 缺陷管理功能正式开始公测
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • JS进阶 - JS 、JS-Web-API与DOM、BOM
  • overflow: hidden IE7无效
  • Spring Boot快速入门(一):Hello Spring Boot
  • VuePress 静态网站生成
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 第2章 网络文档
  • 第十八天-企业应用架构模式-基本模式
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 面试总结JavaScript篇
  • 什么是Javascript函数节流?
  • 使用docker-compose进行多节点部署
  • 数据科学 第 3 章 11 字符串处理
  • 异步
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • zabbix3.2监控linux磁盘IO
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • $(function(){})与(function($){....})(jQuery)的区别
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (3)选择元素——(17)练习(Exercises)
  • (4)STL算法之比较
  • (LeetCode 49)Anagrams
  • (WSI分类)WSI分类文献小综述 2024
  • (笔试题)分解质因式
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (原)本想说脏话,奈何已放下
  • .net core Swagger 过滤部分Api
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .net快速开发框架源码分享
  • .net通用权限框架B/S (三)--MODEL层(2)
  • /etc/motd and /etc/issue
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?
  • []error LNK2001: unresolved external symbol _m
  • [20190401]关于semtimedop函数调用.txt
  • [BZOJ] 2427: [HAOI2010]软件安装