disruptor 笔记 - 高效率原因,为什么快?

Disruptor 为什么快?

原文学习地址http://ifeve.com/locks-are-bad/ 

情景前提:

并发   --  两个线程同时尝试修改同一个变量。

无锁情况,变量值为后修改值。且可能造成线程数据错误。

加锁:悲观锁  -  某一线线程获取变量值,直到它释放锁,另一线程才能获取变量值。 == 锁阻塞,线程越多,系统响应越慢。

    乐观锁  -  某一线程获取变量值,不锁定,当修改值时才锁定。如果修改时变量值已经被修改,则重新获取变量值,再做修改。

死锁: 两个线程需要访问两个相同变量,都在锁定不同的一个变量后去获取另一个变量。

 

Disruptor 为什么高效率?为什么快?实现方式:

 

   Disruptor 不用锁

在线程安全地方,用CASCompare And Swap/Set  比较并交换)操作,CPU 级指令【类似于乐观锁,cpu更新一个值,如果要更改的值被改变,操作失败。】,CAS操作比锁消耗资源小很多【因为不牵扯操作系统】。

Disruptor ClaimStrategy(索赔策略)中,有另个策略:SingleThreadedStrategy 单线程策略,MultiThreadedStrategy 多线程策略。

单线程策略用 long,多线程策略用 AtomicLong (原子 long,是加了synchronized long

重点:在整个复杂框架中,只有一个地方出现多线程竞争修改同一个变量值。

所有访问对象都拥有序号,多线程唯一会被线程竞争写入的序号就是ClaimStrategy对象中的那个。

Disruptor 确保了Entry中每个变量都只被一个消费者写,没有写竞争,就不需要锁 或 CAS

总结:Disruptor 优点

1、没有竞争 - 没有锁 - 非常快

2、访问者记录自己的序号实现方式,允许多个生产者与多个消费者共享相同数据结构

3、每个对象都能跟踪序列号,没有伪共享和非预期竞争。

 

Disruptor的目标是尽可能多的在内存中运行 ,并对缓存行进行填充

原文学习地址http://ifeve.com/disruptor-cacheline-padding/ 

CPU 到内存之间有好几层缓存。L1 一级缓存,L2 二级缓存, L3 三级缓存。

时间消耗对比:

CPU

CPU周期

时间

主存

 

60 - 80 纳秒

L3 cache

40 - 45 cycles 周期

15 ns

L2 cache

10 cycles周期

3 ns

L1 cache

3 - 4 cycle 周期

1 ns

寄存器

1 cycle 周期

 

缓存是由缓存行组成,通常是 64 字节( 2 的整数幂个连续字节 )。(例:Java long 类型是 8字节,一个缓存行可以存 8 long 类型的变量。),内存读取每次会读取一个缓存行,然后对行内数据进行循环读取,期间会无效掉非本线程的数据并重新读取。

伪共享:两个线程下两个不同的变量被存储在同一个缓存行。单个线程写变量时会把整个缓存行都读取出来,然后无效掉非变量的数据,再重新读取。

Disruptor 数据存储会对缓存行进行填充。通过增加补全来确保 ring buffer 的序列号不会和其他东西同时存在于一个缓存中。没有伪共享,没有和其他变量的意外冲突。( 如果访问一个 long 数组,当数组中一个值被加载到缓存中,它会额外加载另外 7个,这样可以非常快速便利在连续内存块中分配的任意数据结构。)

 

:降低 内存屏障 使用

原文学习地址http://ifeve.com/disruptor-memory-barrier/ 

内存屏障 是一个 CPU 指令,作用:

 确保一些特定操作执行的顺序

 影响一些数据的可见性(可能是某些指令执行后的结果)。

编译器 CPU 可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障后,先于这个命令的必须先执行,后于这个命令的必须后执行。即 强制更新一次不同 CPU 的缓存。

Java 中的 Volatile 关键字 (内存屏障)。

使用 volatile 修饰的变量会强制将修改的值 立即写入主存。主存中值更新会使缓存中值失效。

Volatile 具有 可见性、有序性。   没有原子性【 Volatile 没有原子性,是于 synchronized Lock 最大的功能差异。】。

JDK volatile 应用: ConcurrentHashMap Entry 中的 value next  被声明为 volatile

AtomicLong 中的 value 被声明为 volatile

内存屏障有开销(编译器/CPU 不能重拍指令,导致不能最搞笑利用 CPU,另外刷新缓存也有开销),Disruptor 的实现对序列号(多线程中序列号是 AtomicLong类型的)的读写频率尽量降到最低。

 

posted @ 2019-04-23 10:38  currentTimeMillis  阅读(197)  评论(0编辑  收藏  举报