`
invincibleLiu
  • 浏览: 13019 次
  • 性别: Icon_minigender_1
  • 来自: 加泰罗尼亚
社区版块
存档分类
最新评论

关于Java并发包下AQS队列的一点点看法

阅读更多
    No-Blocking算法(简称NB)作为科研的主题已经有20年了,但直到1.5才被大量线上应用;
    我们第一次见到CAS估计都是从那个++引入的:用AtomicInteger和带synchronized关键字的++比看谁加到1000用的时间更少,于是凭借这个小小的volatile int变量我们也就达到了把锁的粒度降到最低、进而达到高并发的目的,然而如果没有CLH队列的保证n个线程疯抢一把对象锁也是很悲剧的。
    进队出队也需要CAS操作,但是没那么简单了.......昨天在看AbstractQueuedSynchronizer源码时发现了一个问题,特此拿出来讨论讨论:
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);//构造代表入队线程的节点
        Node pred = tail;//读取CLH队列最后一个节点(尾指针)
        if (pred != null) {
            node.prev = pred;//CLH是双向链表,但此步不足以入队
//如果从行3到这里尾指针没被其他线程碰过就把尾指针指向入队的那个节点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;//此步足以入队
                return node;
            }
        }
        enq(node);//如果能执行到这里我就有话要说了,且听下文分解
        return node;
    }

    如果哪个线程抢锁失败就不得不执行addWaiter方法了:排队吧~。
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;//再次读取最后一个节点
            if (t == null) { //当且仅当CLH是空的时候t才会为null
                Node h = new Node(); //传说中的头结点(其实是个傀儡节点)
                h.next = node;
                node.prev = h;//入队的节点排在头结点之后
                if (compareAndSetHead(h)) {
                    tail = node;
                    return h;
                }
            }
            else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

    当执行到13行的时候就是亮点了!为什么会执行到这里?因为CLH不为空,再进一步,上一段代码第7行CAS失败了,为什么失败了?因为有个手脚更快的线程抢先一步入队了,尾指针被改变了,这时候就必须把那个手脚快的节点作为尾节点来参照了,剩下的代码就跟上一段的功能一模一样了。
    先撇开Doug Lea的做法不说,要是让你设计插入到队尾怎么设计?当然是经典入队的方法:先把最后一个节点跟新的节点互插:
lastNode.next=newNode;
newNode.pre=lastNode;

然后再让尾指针指向新来的节点;
但是我们看看Doug Lea是怎么搞的,他先在第一段代码的第7行判定能不能把尾指针指向新节点,第8行再把新节点和老节点互插,这看起来是不是有点别扭啊?
    根据我这几年的经验,别扭的流程一般都不会获得最速度的执行,我左思右想终于发现Doug Lea设计的缺陷了:一旦addWaiter方法第七行CAS失败那这个方法也就废了,所有任务都将抛给enq方法去执行,这段流程也就没有意义了,而且enq方法的设计是CAS使用的大忌:在while(true)这样的死循环里进行CAS比较,完全违背了建立CLH队列的初衷——防止线程的恶性竞争。
    对此我提出了一个淫蛋一点的方法:在最开始的时候,先作如下判断:
if(tail.next==null){...

可能这时候有人会喷我了,这个用判断吗?别急,如果此判断为true,那我就放心大胆的按常规思路先走第一步,新老节点互插,完成入队,接着尾指针指向新节点;如果为false,这就说明此队列处于不一致状态,那我就先把他置为一致状态再说,下面呈上代码:
public Node addWaiter(Node newNode) {
        while (true) {
            if (tail.next == null) {//看起来很“废”的判断
                if (tail.next.CAS(null, newNode)) {//完成入队
                    CAStail(tail, newNode);//刷新尾指针
                    return newNode;
                }
            }else{
                CAStail(tail,tail.next);//让队列回到一致状态
            }
        }
}

如果看了上述代码仍不能理解不一致状态,那我就贴上图了
可能有人要问了,你这里不也是把CAS放到while(true)里面吗?没错,我是这么做了,但是我的这个粒度要比Doug Lea的小得多,他的addWaiter第七行在高并发情况下很容易失败,一旦失败就要全部重来(经典入队的两大步),而我的至少能保证第一步很容易完成,第二步无需完成(刷新尾指针的任务其他线程可以智能地帮我完成)。
  jdk1.7已经发布了,我的发现注定赶不上这班车了,但我希望1.8能考虑一下这个小小的发现......
分享到:
评论

相关推荐

    Java并发之AQS详解.pdf

    AbstractQueuedSynchronizer(AQS)是 Java 并发编程中的一个核心组件,提供了一套多线程访问共享资源的同步器框架。AQS 定义了两种资源共享方式:Exclusive(独占)和 Share(共享)。在 AQS 中,维护了一个 ...

    Java并发编程:深入解析抽象队列同步器(AQS)及其在Lock中的应用

    本文深入探讨了Java并发编程的关键组件——抽象队列同步器(AQS)及其在ReentrantLock的应用。AQS是处理线程同步问题的高效工具,是Java并发编程中的核心。文章首先简要介绍了并发编程领域的先驱Doug Lea。重点在于...

    java并发编程-AQS和JUC实战

    ### Java并发编程-AQS和JUC实战 #### 一、ReentrantLock 重入锁 **1.1 概述** - **基本介绍**: `ReentrantLock` 是一个实现了 `Lock` 接口的可重入互斥锁,提供比 `synchronized` 更丰富的功能。与 `synchronized...

    java并发编程:juc、aqs

    Java并发编程中的`JUC`(Java Util Concurrency)库是Java平台中用于处理多线程问题的核心工具包,它提供了一系列高效、线程安全的工具类,帮助开发者编写并发应用程序。`AQS`(AbstractQueuedSynchronizer)是JUC库中的...

    Java并发编程解析 | 解析AQS基础同步器的设计与实现

    "Java并发编程解析 | 解析AQS基础同步器的设计与实现" 在Java领域中,解决并发编程问题的关键是解决同步和互斥的问题。同步是指线程之间的通信和协作,互斥是指同一时刻只能允许一个线程访问共享资源。Java领域中有...

    Java 并发编程实战.pdf

    Java 5及以上版本引入了java.util.concurrent包,这个包提供了大量的并发工具类,例如Executor框架用于管理线程池,ConcurrentHashMap、CopyOnWriteArrayList等并发集合,以及原子类如AtomicInteger等。这些工具类极...

    JAVA并发编程与高并发解决方案-并发编程四之J.U.C之AQS.docx

    《JAVA并发编程与高并发解决方案-并发编程四之J.U.C之AQS》是一篇详细介绍Java实用并发工具包(Java Util Concurrency,简称J.U.C.)中重要组成部分——AbstractQueuedSynchronizer(简称AQS)的文章。AQS是Java并发...

    Java并发编程全景图.pdf

    Java提供了丰富的并发框架来简化并发编程,例如AbstractQueuedSynchronizer(AQS)是一个用来构建锁和其他同步类的基础框架。java.util.concurrent包中提供了诸如CopyOnWriteArrayList、ConcurrentHashMap等线程安全...

    Java并发编程学习笔记

    AQS是Java并发包中的抽象队列同步器,它是ReentrantLock、Semaphore等并发工具的基础,通过维护一个FIFO等待队列来管理线程的等待和唤醒。 9. **CAS(Compare and Swap)**: CAS是一种无锁算法,用于更新变量。...

    Java并发系列之AbstractQueuedSynchronizer源码分析(条件队列)

    在Java并发编程中,`AbstractQueuedSynchronizer`(AQS)是一个重要的抽象类,用于构建锁和其他同步组件。AQS的核心是通过一个整型变量`state`来表示同步状态,并利用双端队列(FIFO)管理等待的线程。在本篇中,我们...

    AQS抽象队列同步器,AQS抽象队列同步器

    AQS抽象队列同步器,AQS抽象队列同步器

    深入浅出_Java并发工具包原理讲解

    Java并发工具包(J.U.C)是Java编程语言中用于并发编程的一系列工具包的统称,它包含了一系列方便实现多线程编程的类和接口,使得开发者可以更加方便地编写高效、线程安全的程序。本文将深入浅出地探讨J.U.C的原理和...

    Java-并发(Concurrent)编程

    5. **Java并发工具包(JUC)**:包括原子类、并发容器(如`ConcurrentHashMap`)、阻塞队列(如`ArrayBlockingQueue`)以及锁机制。 6. **Lock和AQS**:Lock接口提供了比`synchronized`更灵活的锁机制,而...

    Java 多线程与并发(10-26)-JUC锁- 锁核心类AQS详解.pdf

    Java并发包(java.util.concurrent,简称JUC)提供了一系列工具和类库来帮助开发者简化并发编程的工作。其中,AbstractQueuedSynchronizer(简称AQS)是构建各种同步器的核心组件。 AQS是一个抽象的队列同步器,它...

    Java并发编程:用AQS写一把可重入锁

    AQS是J.U.C包下AbstractQueuedSynchronizer抽象的队列式的同步器的简称,这是一个抽象类,它定义了一套多线程访问共享资源的同步器框架,J.U.C包下的许多同步类实现都依赖于它,比如ReentrantLock/Semaphore/...

    aqs_java_

    《Java并发编程:深入理解AQS》 在Java编程领域,多线程和并发处理是不可或缺的一部分,而Java.util.concurrent库则是实现并发控制的核心工具。本文将深入探讨该库中的重要组件——AbstractQueuedSynchronizer(AQS...

    Java并发 结合源码分析AQS原理

    Java并发编程中,AQS(AbstractQueuedSynchronizer)是一个核心组件,它提供了一个基于FIFO队列和状态变量的基础框架,用于构建锁和其他同步装置。在这篇文章中,我们将深入探讨AQS的原理和实现机制,并结合源码分析...

    龙果java并发编程完整视频

    第1节你真的了解并发吗? [免费观看][免费观看] 00:27:48分钟 | 第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看]...

Global site tag (gtag.js) - Google Analytics