讲义整理-Java8新增的并发 た 入场券 2023-01-14 05:59 177阅读 0赞 ### 讲义整理-Java8新增的并发 ### * Java8新增的并发 * * 原子操作 CAS * * LongAdder * 其他新增 * StampLock * CompleteableFuture * * Future 的不足 * CompleteableFuture * 补充:Lambda 速成 * * 第一步 * 第二步 * 第三步 * Lambda * 函数描述符 * 扩充知识点- Disrupto * * 应用背景和介绍 * 传统队列问题 * 高性能的原理 -------------------- # Java8新增的并发 # ## 原子操作 CAS ## ### LongAdder ### JDK1.8 时,java.util.concurrent.atomic 包中提供了一个新的原子类:LongAdder。根据 Oracle 官方文档的介绍,LongAdder 在高并发的场景下会比它的前辈————AtomicLong 具有更好的性能,代价是消耗更多的内存空间。 **AtomicLong** 是利用了底层的 CAS 操作来提供并发性的,调用了 **Unsafe** 类的 **getAndAddLong** 方法,该方法是个 **native** 方法,它的逻辑是采用自旋的方式不断 更新目标值,直到更新成功。 在并发量较低的环境下,线程冲突的概率比较小,自旋的次数不会很多。但是,高并发环境下,N 个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时 AtomicLong 的自旋会成为瓶颈。 这就是 **LongAdder** 引入的初衷——解决高并发环境下 **AtomicLong** 的自旋瓶颈问题。 **AtomicLong** 中有个内部变量 **value** 保存着实际的 long 值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value 变量其实是一个热点,也就是 N 个线程竞争一个热点。 ![在这里插入图片描述][20210418002850273.png] LongAdder 的基本思路就是分散热点,将 value 值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行 CAS 操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的 long 值,只要将各个槽中的变量值累加返回。 这种做法和 ConcurrentHashMap 中的“分段锁”其实就是类似的思路。 **LongAdder** 提供的 API 和 **AtomicLong** 比较接近,两者都能以原子的方式对long 型变量进行增减。 但是 **AtomicLong** 提供的功能其实更丰富,尤其是 **addAndGet、decrementAndGet、compareAndSet** 这些方法。 **addAndGet、decrementAndGet** 除了单纯的做自增自减外,还可以立即获取增减后的值,而 **LongAdder** 则需要做同步控制才能精确获取增减后的值。如果业务需求需要精确的控制计数,做计数比较,**AtomicLong** 也更合适。 另外,从空间方面考虑,**LongAdder** 其实是一种“空间换时间”的思想,从这一点来讲 **AtomicLong** 更适合。 总之,低并发、一般的业务场景下 AtomicLong 是足够了。如果并发量很多,存在大量写多读少的情况,那 LongAdder 可能更合适。适合的才是最好的,如果真出现了需要考虑到底用 AtomicLong 好还是 LongAdder 的业务场景,那么这样的讨论是没有意义的,因为这种情况下要么进行性能测试,以准确评估在当前业务场景下两者的性能,要么换个思路寻求其它解决方案。 对于 **LongAdder** 来说,内部有一个 base 变量,一个 Cell\[\]数组。 base 变量:非竞态条件下,直接累加到该变量上。 Cell\[\]数组:竞态条件下,累加个各个线程自己的槽 Cell\[i\]中。 ![在这里插入图片描述][20210418220149847.png] ![在这里插入图片描述][20210418220209384.png] 所以,最终结果的计算应该是 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70] ![在这里插入图片描述][20210418220447686.png] 在实际运用的时候,只有从未出现过并发冲突的时候,base 基数才会使用到,一旦出现了并发冲突,之后所有的操作都只针对 Cell\[\]数组中的单元 Cell。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 1] 而 LongAdder 最终结果的求和,并没有使用全局锁,返回值不是绝对准确的,因为调用这个方法时还有其他线程可能正在进行计数累加,所以只能得到某个时刻的近似值,这也就是 LongAdder 并不能完全替代 LongAtomic 的原因之一。 而且从测试情况来看,线程数越多,并发操作数越大,LongAdder 的优势越大,线程数较小时,AtomicLong 的性能还超过了 LongAdder。 ### 其他新增 ### 除了新引入 LongAdder 外,还有引入了它的三个兄弟类:**LongAccumulator、DoubleAdder、DoubleAccumulator**。 LongAccumulator 是 LongAdder 的增强版。LongAdder 只能针对数值的进行加减运算,而 LongAccumulator 提供了自定义的函数操作。 通过 LongBinaryOperator,可以自定义对入参的任意操作,并返回结果(LongBinaryOperator 接收 2 个 long 作为参数,并返回 1 个 long)。 LongAccumulator 内部原理和 LongAdder 几乎完全一样。 DoubleAdder 和 DoubleAccumulator 用于操作 double 原始类型。 ### StampLock ### StampedLock是Java8引入的一种新的所机制,简单的理解,可以认为它是读写锁的一个改进版本,读写锁虽然分离了读和写的功能,使得读与读之间可以完全并发,但是读和写之间依然是冲突的,读锁会完全阻塞写锁,它使用的依然是悲观的锁策略.如果有大量的读线程,他也有可能引起写线程的饥饿。 而 StampedLock 则提供了一种乐观的读策略,这种乐观策略的锁非常类似于无锁的操作,使得乐观锁完全不会阻塞写线程。 它的思想是读写锁中读不仅不阻塞读,同时也不应该阻塞写。 **读不阻塞写的实现思路:** 在读的时候如果发生了写,则应当重读而不是在读的时候直接阻塞写!即读写之间不会阻塞对方,但是写和写之间还是阻塞的! StampedLock 的内部实现是基于 CLH 的。 参考代码,参见 cn.enjoyedu.cha. StampedLockDemo ## CompleteableFuture ## ### Future 的不足 ### Future 是Java 5添加的类,用来描述一个异步计算的结果。你可以使用 isDone方法检查计算是否完成,或者使用 get 阻塞住调用线程,直到计算完成返回结果,你也可以使用 cancel 方法停止任务的执行。 虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果, **为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?** Java 的一些框架,比如 Netty,自己扩展了 Java 的 Future 接口,提供了addListener 等多个扩展方法,Google guava 也提供了通用的扩展Future:ListenableFuture、SettableFuture 以及辅助类 Futures 等,方便异步编程。 同时 Future 接口很难直接表述多个 Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的: 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。 等待 Future 集合中的所有任务都完成。 仅等待 Future 集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。 应对 Future 的完成事件(即当 Future 的完成事件发生时会收到通知,并能使用 Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果) ### CompleteableFuture ### JDK1.8 才新加入的一个实现类 CompletableFuture,实现了Future,CompletionStage两个接口。实现了 Future 接口,意味着可以像以前一样通过阻塞或者轮询的方式获得结果。 **创建** 除了直接 new 出一个 CompletableFuture 的实例,还可以通过工厂方法创建CompletableFuture 的实例 **工厂方法:** ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 2] Asynsc表示异步,而supplyAsync与runAsync 不同在与前者异步返回一个结果, 后者是 void.第二个函数第二个参数表示是用我们自己创建的线程池,否则采用默认的 ForkJoinPool.commonPool()作为它的线程池。 **获得结果的方法** * public T get() * public T get(long timeout, TimeUnit unit) * public T getNow(T valueIfAbsent) * public T join() getNow 有点特殊,如果结果已经计算完则返回结果或者抛出异常,否则返回给定的 valueIfAbsent 值。 join 返回计算的结果或者抛出一个 unchecked 异常(CompletionException),它和 get 对抛出的异常的处理有些细微的区别。 参见 cn.enjoyedu.cha.cfdemo 下 CFDemo 和 JoinAndGet 辅助方法 * public static CompletableFuture allOf(CompletableFuture<?>… cfs) * public static CompletableFuture anyOf(CompletableFuture<?>… cfs) * allOf 方法是当所有的 CompletableFuture 都执行完后执行计算。 * anyOf 方法是当任意一个 CompletableFuture 执行完后就会执行计算,计算的结果相同。 参见 cn.enjoyedu.cha.cfdemo 下 AllofAnyOf CompletionStage 是一个接口,从命名上看得知是一个完成的阶段,它代表了一个特定的计算的阶段,可以同步或者异步的被完成。你可以把它看成一个计算流水线上的一个单元,并最终会产生一个最终结果,这意味着几个CompletionStage 可以串联起来,一个完成的阶段可以触发下一阶段的执行,接着触发下一次,再接着触发下一次,……….。 总结 CompletableFuture 几个关键点: * 1、计算可以由 Future ,Consumer 或者 Runnable 接口中的 apply,accept或者 run 等方法表示。 * 2、计算的执行主要有以下 * a. 默认执行 * b. 使用默认的 CompletionStage 的异步执行提供者异步执行。这些方法名使用 someActionAsync 这种格式表示。 * c. 使用 Executor 提供者异步执行。这些方法同样也是 someActionAsync 这种格式,但是会增加一个 Executor 参数。 CompletableFuture 里大约有五十种方法,但是可以进行归类, * 变换类 thenApply ![\-][-] 关键入参是函数式接口 Function。它的入参是上一个阶段计算后的结果,返回值是经过转化后结果。 * 消费类 thenAccept: ![在这里插入图片描述][202104182219570.png] 关键入参是函数式接口 Consumer。它的入参是上一个阶段计算后的结果,没有返回值。 * 执行操作类 thenRun: ![在这里插入图片描述][20210418222034574.png] 对上一步的计算结果不关心,执行下一个操作,入参是一个 Runnable 的实例,表示上一步完成后执行的操作。 * 结合转化类: ![在这里插入图片描述][20210418222120387.png] 需要上一步的处理返回值,并且 other 代表的 CompletionStage 有返回值之后,利用这两个返回值,进行转换后返回指定类型的值。 两个 CompletionStage 是并行执行的,它们之间并没有先后依赖顺序,other并不会等待先前的 CompletableFuture 执行完毕后再执行 * 结合转化类 ![在这里插入图片描述][20210418222201384.png] 对于 Compose 可以连接两个 CompletableFuture,其内部处理逻辑是当第一个 CompletableFuture 处理没有完成时会合并成一个 CompletableFuture,如果处理完成,第二个 future 会紧接上一个 CompletableFuture 进行处理。 第一个 CompletableFuture 的处理结果是第二个 future 需要的输入参数。 * 结合消费类: ![在这里插入图片描述][20210418222234956.png] 需要上一步的处理返回值,并且 other 代表的 CompletionStage 有返回值之 后,利用这两个返回值,进行消费 * 运行后执行类: ![在这里插入图片描述][20210418222312232.png] 不关心这两个 CompletionStage 的结果,只关心这两个 CompletionStage 都执行完毕,之后再进行操作(Runnable)。 * 取最快转换类: ![在这里插入图片描述][20210418222408406.png] 两个 CompletionStage,谁计算的快,我就用那个 CompletionStage 的结果进行下一步的转化操作。现实开发场景中,总会碰到有两种渠道完成同一个事情,所以就可以调用这个方法,找一个最快的结果进行处理。 * 取最快消费类: ![在这里插入图片描述][20210418222454649.png] 两个 CompletionStage,谁计算的快,我就用那个 CompletionStage 的结果进行下一步的消费操作。 * 取最快运行后执行类: ![在这里插入图片描述][20210418222528839.png] 两个 CompletionStage,任何一个完成了都会执行下一步的操作(Runnable)。 * 异常补偿类: ![在这里插入图片描述][20210418222603634.png] 当运行时出现了异常,可以通过 exceptionally 进行补偿。 * 运行后记录结果类: ![在这里插入图片描述][20210418222635172.png] action 执行完毕后它的结果返回原始的 CompletableFuture 的计算结果或者返回异常。所以不会对结果产生任何的作用。 * 运行后处理结果类: ![在这里插入图片描述][20210418222706725.png] 运行完成时,对结果的处理。这里的完成时有两种情况,一种是正常执行,返回值。另外一种是遇到异常抛出造成程序的中断。 ## 补充:Lambda 速成 ## 本补充章节仅为没接触过 Lambda 的同学快速入门和速查,更具体的 Lamba的知识请自行查阅相关书籍和博客。 相关代码放在 cn.enjoyedu.cha.lambda 下 现在我们有一个实体类,我们会对这个实体类进行操作。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 3] ### 第一步 ### 我们想从一批 Circle 中挑选出挑选出半径为 2 的圆,于是我们写了一个方法 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 4] 这样,无疑很不优雅,如果我们想挑选半径为 3 的圆,难道还要再写一个方法?于是我们考虑将选择条件进行参数化,比如根据颜色挑选出圆或者根据半径挑选出圆 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 5] ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 6] 但是,这种实现,还是有问题的,1、选择条件变化了,那么相应的方法也要变,比如我们想挑选半径大于 3 的圆,怎么办?如果我要根据多个条件选择,怎么办?难道把所有的条件都传入吗?于是,我们考虑定义一个挑选圆的接口,程序进化到了第二歩 ### 第二步 ### 进行行为参数化,定义一个接口 ![在这里插入图片描述][20210418223010866.png] 在进行圆的挑选的方法里,我们把这个接口作为参数进行传递 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 7] 然后,我们只要按业务需求实现接口,并传入实现类的实例即可 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 8] 这种方式可以提高灵活性,但是业务上每增加一个挑选行为, 我们就需要显式声明一个接口 ChoiceCircle 的实现类,于是我们可以考虑使用内部匿名类,进入第三步。 ### 第三步 ### 在实际使用时,我们不再声明一个接口 ChoiceCircle 的实现 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 9] 匿名内部类占用代码空间较多,而且存在着模版代码,这种情况下,Lambda表达式就可以派上用场了 ![在这里插入图片描述][20210418223421366.png] 所以可以把 Lambda 表达式看成匿名内部类的一个简洁写法 ### Lambda ### 在语法上,Lambda 表达式包含三个部分,参数列表,箭头,主体,比如: **(parameters) -> expression** 或 **(parameters) -> {statements;}** Lambda 表达式用在函数式接口上,所谓函数式接口,是只定义了一个抽象方法的接口(Interface),接口中是否有默认方法,不影响。 注解@FunctionalInterface 可以帮助我们在设计函数式接口时防止出错。 我们常用的 Runnable,Callable 都是函数式接口,JDK8 中新增了几个函数式接口: **Predicate :** 包含 test 方法,接受泛型的 T,返回 boolean,可以视为断言(检查)接口 **Consumer :** 包含 accept 方法,接受泛型的 T,无返回,可以视为数据消费接口 **Function<T,R> :** 包含 apply 方法,接受泛型的 T,返回 R,可以视为映射转换接口 **Supplier** 包含 get 方法,无输入,返回 T,可以视为创建一个新对象接口 **UnaryOperator** 扩展至 Function<T,T>,所以这个本质上也是一个映射转换接口,只不过映射转换后的类型保持不变 **BiFunction<T, U, R>** 包含 apply 方法,接受泛型的 T、U,返回 R,可以视为复合型映射转换接口 **BinaryOperator** 扩展至 Function BiFunction<T,T,T>,所以这个本质上也是一个复合型映射转换接口,只不过映射转换后的类型保持不变 **BiPredicate <T, U>** 包含 test 方法,接受泛型的 T,U,返回 boolean,可以视为复合型断言(检查)接口 **BiConsumer<T,U>:** 包含 accept 方法,接受泛型的 T,U,无返回,可以视为复合型数据消费接口 同时还提供了一些为了防止自动装箱机制,而特意声明的原始类型特化的函数式接口,比如, ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 10] 在意义上,和对应的 Predicate 接口并没有差别。 ### 函数描述符 ### 函数式接口的抽象方法的签名基本上就是 Lambda 表达式的签名。我们将这种抽象方法叫作函数描述符。 Runnable 接口可以看作一个什么也不接受什么也不返回(void)的函数的签名,因为它只有一个叫作 run 的抽象方法,这个方法什么也不接受,什么也不返回(void)。 我们可以用 () -> void 代表参数列表为空,且返回 void 的函数。这正是Runnable 接口所代表的。我们于是可以称() -> void 是 Runnable 接口的函数描述符。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 11] 再考察 Callable 接口和 Supplier 接口 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 12] ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 13] 从函数描述符来看,Callable 接口和 Supplier 接口是一样的,都是() -> X 所以同一个 Lambda 可以同时用在这两个函数式接口上,比如: Callable = () -> 33; Supplier<> = () -> 33; -------------------- # 扩充知识点- Disrupto # ## 应用背景和介绍 ## Disruptor 是英国外汇交易公司 LMAX 开发的一个高性能队列,研发的初衷是解决内部的内存队列的延迟问题,而不是分布式队列。基于 Disruptor 开发的系统单线程能支撑每秒 600 万订单,2010 年在 QCon 演讲后,获得了业界关注。 据目前资料显示:应用 Disruptor 的知名项目有如下的一些:Storm, Camel, Log4j2,还有目前的美团点评技术团队也有很多不少的应用,或者说有一些借鉴了它的设计机制。 Disruptor 是一个高性能的线程间异步通信的框架,即在同一个 JVM 进程中的多线程间消息传递。 ## 传统队列问题 ## 在 JDK 中,Java 内部的队列 BlockQueue 的各种实现,仔细分析可以得知,队列的底层数据结构一般分成三种:数组、链表和堆,堆这里是为了实现带有优先级特性的队列暂且不考虑。 在稳定性和性能要求特别高的系统中,为了防止生产者速度过快,导致内存溢出,只能选择有界队列;同时,为了减少 Java 的垃圾回收对系统性能的影响,会尽量选择 Array 格式的数据结构。这样筛选下来,符合条件的队列就只有ArrayBlockingQueue。但是 ArrayBlockingQueue 是通过加锁的方式保证线程安全,而且 ArrayBlockingQueue 还存在伪共享问题,这两个问题严重影响了性能。 ArrayBlockingQueue 的这个伪共享问题存在于哪里呢,分析下核心的部分源码,其中最核心的三个成员变量为 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 14] 是在 ArrayBlockingQueue 的核心 enqueue 和 dequeue 方法中经常会用到的,这三个变量很容易放到同一个缓存行中,进而产生伪共享问题。 ## 高性能的原理 ## * 引入环形的数组结构:数组元素不会被回收,避免频繁的 GC, * 无锁的设计:采用 CAS 无锁方式,保证线程的安全性 * 属性填充:通过添加额外的无用信息,避免伪共享问题 环形数组结构是整个 Disruptor ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 15] 首先因为是数组,所以要比链表快,而且根据我们对上面缓存行的解释知道,数组中的一个元素加载,相邻的数组元素也是会被预加载的,因此在这样的结构中,cpu 无需时不时去主存加载数组中的下一个元素。而且,你可以为数组预先分配内存,使得数组对象一直存在(除非程序终止)。这就意味着不需要花大量的时间用于垃圾回收。此外,不像链表那样,需要为每一个添加到其上面的对象创造节点对象—对应的,当删除节点时,需要执行相应的内存清理操作。环形数组中的元素采用覆盖方式,避免了 jvm 的 GC。 其次结构作为环形,数组的大小为 2 的 n 次方,这样元素定位可以通过位运算效率会更高,这个跟一致性哈希中的环形策略有点像。在 disruptor 中,这个牛逼的环形结构就是 RingBuffer,既然是数组,那么就有大小,而且这个大小必须是 2 的 n 次方 其实质只是一个普通的数组,只是当放置数据填充满队列(即到达 2^n-1 位置)之后,再填充数据,就会从 0 开始,覆盖之前的数据,于是就相当于一个环。 每个生产者首先通过 CAS 竞争获取可以写的空间,然后再进行慢慢往里放数据,如果正好这个时候消费者要消费数据,那么每个消费者都需要获取最大可消费的下标。 同时,Disruptor 不像传统的队列,分为一个队头指针和一个队尾指针,而是只有一个角标(上图的 seq),它属于一个 volatile 变量,同时也是我们能够不用锁操作就能实现 Disruptor 的原因之一,而且通过缓存行补充,避免伪共享问题。该指针是通过一直自增的方式来获取下一个可写或者可读数据。 [20210418002850273.png]: /images/20221022/9ac4681092e046a2beb5f78543700752.png [20210418220149847.png]: /images/20221022/ffaa83d06fa44e0d8b1f4928d5e0b5b2.png [20210418220209384.png]: /images/20221022/5e93b64cc081405491aea04d9f36ad55.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70]: /images/20221022/f2c8b74496c7478591d8a5566cee418f.png [20210418220447686.png]: /images/20221022/d096fa6334484aa79945428831d6b124.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 1]: /images/20221022/fde8950a4568447fb07a4a9a0201a586.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 2]: /images/20221022/c03e86f582b4436da6f0b61da45bc16a.png [-]: /images/20221022/0bfa9b385ebe41b0a21a719ef0feefc9.png [202104182219570.png]: /images/20221022/38a96e57aeaa4a18b797e152e08b7486.png [20210418222034574.png]: /images/20221022/633d1994b5594b63908405015ab7fc38.png [20210418222120387.png]: /images/20221022/92064596f4354c5cb74b0508b2c24244.png [20210418222201384.png]: /images/20221022/b704e465260446ff820452786cfe4cb8.png [20210418222234956.png]: /images/20221022/38e4c857242d492da55425c4bec2a912.png [20210418222312232.png]: /images/20221022/98a7c440c6fd427ca244fdd3af0b7f63.png [20210418222408406.png]: /images/20221022/ad1ef49377e44c7bbf07c7d965756e13.png [20210418222454649.png]: /images/20221022/3eb0abb3f3f74ddcabeab856e04a28fe.png [20210418222528839.png]: /images/20221022/a7ed1f6e8799463ab04f2bb271dfa968.png [20210418222603634.png]: /images/20221022/370c87367e3a4396807c8e35d9c6dfab.png [20210418222635172.png]: /images/20221022/802a9965d0d74a5bbbeea2e4d731fcdc.png [20210418222706725.png]: /images/20221022/d06b46d1c7da4a7cb2bf0045ff012b80.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 3]: /images/20221022/c015ac38f91d4a4bbb695a9c260c474c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 4]: /images/20221022/46c0229a1870442abe0fe04cd1f4a70f.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 5]: /images/20221022/870c76f5e34b423abfcc968bbf8387e3.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 6]: /images/20221022/8f777c79190b480699ec91ddebbaa2fe.png [20210418223010866.png]: /images/20221022/fb5a7c8855e542ab8a7b1f59ced561ab.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 7]: /images/20221022/c8761b04318240fa9e5400866321a914.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 8]: /images/20221022/44eee367bf8b402384b84cd4312dd320.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 9]: /images/20221022/bace39ec0a4f4dea9824b894b4976e48.png [20210418223421366.png]: /images/20221022/f62ef975860e4e5fa4f8eb9e5d828412.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 10]: /images/20221022/3529e34b37b54b69a8861b74d54aa812.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 11]: /images/20221022/35a3b1721a4448c39d2a78122b591fe0.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 12]: /images/20221022/78f1caff70684024b90a1f904a704d14.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 13]: /images/20221022/d9401671cedc4ad48b9143bca06db76f.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 14]: /images/20221022/6cdce5c381ae42e09e95071fb26227f5.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tlZW5zdHlsZQ_size_16_color_FFFFFF_t_70 15]: /images/20221022/32c429c6e77746718f29c34265c87dd6.png
相关 讲义整理-常见并发面试题 讲义整理-总复习和常见并发面试题 总复习和常见并发面试题 谈面试 常见面试题 在 java 中守护线程和用户线程的区别 拼搏现实的明天。/ 2023年01月14日 06:55/ 0 赞/ 170 阅读
相关 讲义整理-Java8新增的并发 讲义整理-Java8新增的并发 Java8新增的并发 原子操作 CAS LongAdder 其他新增 た 入场券/ 2023年01月14日 05:59/ 0 赞/ 178 阅读
相关 讲义整理-并发任务执行框架 讲义整理-并发任务执行框架 实战--并发任务执行框架 架构师是什么? 主要职责 架构师的方方面面 比眉伴天荒/ 2023年01月14日 05:58/ 0 赞/ 167 阅读
相关 讲义整理-线程池 讲义整理-线程池 线程池 为什么要用线程池? ThreadPoolExecutor 的类关系 线程池的创建各个参数含义 冷不防/ 2023年01月14日 05:56/ 0 赞/ 134 阅读
相关 讲义整理-原子操作 CAS 讲义整理-并发编程 原子操作 CAS 什么是原子操作?如何实现原子操作? CAS 是怎么实现线程的安全呢? CAS 实 向右看齐/ 2023年01月14日 05:53/ 0 赞/ 216 阅读
相关 讲义整理-线程的并发工具类 讲义整理-并发编程 线程的并发工具类 Fork-Join 分而治之 归并排序 归并排序(降序)示 桃扇骨/ 2023年01月14日 05:52/ 0 赞/ 142 阅读
相关 讲义整理-并发编程 本文是按照自己学习时老师讲义内容整理而来,很多内容过于复杂没有整理完全。 -------------------- [线程基础、线程之间的共享和协作][Link 1] 怼烎@/ 2022年11月19日 01:13/ 0 赞/ 155 阅读
相关 java8 新增的@Repeatable注解 1. \ java8 新增的@Repeatable注解,其实只是语法糖而已. 2. \ java8 注解的 \{ @link RepeatAnn\} 类与 \{ @l 心已赠人/ 2021年09月11日 06:14/ 0 赞/ 393 阅读
还没有评论,来说两句吧...