【内存队列】-锁&内存队列

无锁算法 - CAS

CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。

  • CAS是乐观锁的核心,它是一个不断尝试的机制,不用锁任何资源。
  • CAS的队列都是无界;有界队列应该会加锁处理边界问题。

内存值V,旧的预期值A,要修改的新值B。

当且仅当 【预期值A = 内存值V】时,将内存值V修改为B,否则什么都不做。
在这里插入图片描述

Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问

JDK内置队列 - 速览

数据结构 来看,来保证线程安全的方式

  • 基于 数组 线程安全的队列
    • (ArrayBlockingQueue),加锁
  • 基于 链表 线程安全队列
    • LinkedBlockingQueue 加锁
    • ConcurrentLinkedQueue 原子变量的CAS 不加锁(若存在伪共享问题,推荐用Disruptor)
  • 基于 堆 线程安全队列 (加锁)
    • DelayQueue 延迟队列
    • PriorityBlockingQueue 优先级队列

但是对 volatile类型的变量进行 CAS 操作,存在伪共享问题。 花开两朵,各表一枝(逃
在这里插入图片描述

加锁队列/有界队列

ArrayBlockingQueue

在这里插入图片描述

伪共享

聊聊CPU缓存、伪共享与缓存行填充

当生产者线程put一个元素到ArrayBlockingQueue时, putIndex会修改, 从而导致消费者线程的缓存中的缓存行无效, 需要从主存中重新读取。这种无法充分使用缓存行特性的现象, 称为伪共享。

使用数组来维护VolatileLong,故它们在内存中是连续存储的。
由于该类内的value成员已经使用volatile关键字来修饰,故CPU要保证它的修改对所有线程都立即可见。
value的自增值会直接回写到内存,并将对应的缓存行置为失效
缓存行

以缓存行(cache line)为单位存储的(2^N幂个连续字节),最常见的缓存行大小是64个字节。

多线程修改 【同一缓存行 的变量】,会无意中影响彼此的性能

@Contended

Disruptor以及@Contended注解

Java 8用@Contended在【类/字段】上的注释,来进行【缓存行】填充,解决多线程下的伪共享冲突。

-XX:-RestrictContended 添加这个参数才能够开启Contented

@Contended("group1")
private Object field1;

@Contended("group1")
private Object field2;

@Contended("group2")
private Object field3;

无界队列

Disruptor

高性能队列——Disruptor

代码案例

设计方案

(1)环形数组结构
为了避免垃圾回收,采用数组而非链表。
数组遍历更快(CPU的多级缓存机制实现原理,PageCache缓存数据的邻近数据)

(2)元素位置定位
数组长度2^n,通过位运算,加快定位的速度
下标采取递增的形式(不用担心index溢出,long类型,100万QPS也需30万年)

(3)无锁设计
每个 生产者/消费者 线程,先申请可以操作的元素的index

伪内存共享
class LhsPadding {
	protected long p1, p2, p3, p4, p5, p6, p7;
}

class Value extends LhsPadding {
	protected volatile long value; // 核心 + 前后15个中的7个无关的long >= 64B
}

class RhsPadding extends Value {
	protected long p9, p10, p11, p12, p13, p14, p15;
}

public classSequenceextends RhsPadding {
	static final long INITIAL_VALUE = -1L;
	private static final Unsafe UNSAFE;
	private static final long VALUE_OFFSET;
	static {
		UNSAFE = Util.getUnsafe();
		try {
			VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));
		} catch(final Exception e) {
			 throw new RuntimeException(e);
		}
	}
}
无锁队列实现

单生产者:比较简单

多生产者:

每个线程获取不同的一段数组空间进行操作(在分配元素的时,通过CAS判断这段空间是否已被分配)

多生产者下,引入一个与Ring Buffer大小相同的buffer:available Buffer(替身)
当某个位置写入成功的时候,便把availble Buffer相应的位置置位,标记为写入成功。
读取时,会遍历available Buffer,来判断元素是否已经就绪。

等待策略
生产者的等待策略

暂时只有休眠1ns。

LockSupport.parkNanos(1);
消费者的等待策略

(1)BlockingWaitStrategy(默认),是效率最低的等待策略,但也是CPU使用率最低和最稳定的选项
(2)BusySpinWaitStrategy,是性能最高的等待策略,同时也对部署环境要求最高
(3)YieldingWaitStrategy,需要高性能而且事件消费者线程比逻辑内核少
(4)SleepingWaitStrategy,不需要低延迟,且事件影响比较小。比如异步日志功能。
在这里插入图片描述

生产消费模式

并发系统中提高性能最好的方式之一就是单一写者原则
生产者
在这里插入图片描述

Disruptor<LongEvent> disruptor = new Disruptor(factory, 
                bufferSize, 
                ProducerType.SINGLE, // 单一写者模式, 
                executor
);

消费者
(1)串行消费
在这里插入图片描述

 public static void serial(Disruptor<LongEvent> disruptor){
     disruptor.handleEventsWith(new C1_EventHandler()).then(new C2_EventHandler());
     disruptor.start();
}     

(2)菱形执行
在这里插入图片描述

 public static void diamond(Disruptor<LongEvent> disruptor){
     disruptor.handleEventsWith(new C1_EventHandler(),new C2_EventHandler())
     		  .then(new C3_EventHandler());
     disruptor.start();
 }

(3)相互隔离
在这里插入图片描述

 public static void parallelWithPool(Disruptor<LongEvent> disruptor){
     disruptor.handleEventsWithWorkerPool(new C11EventHandler(),new C11EventHandler());
     disruptor.handleEventsWithWorkerPool(new C21EventHandler(),new C21EventHandler());
     
     disruptor.start();
 }

(3)航道模式
在这里插入图片描述

 /**
   * 串行依次执行,同时C11,C21分别有2个实例
   * 
   * p --> c11 --> c21
   * @param disruptor
   */
  public static void serialWithPool(Disruptor<LongEvent> disruptor){
      disruptor.handleEventsWithWorkerPool(new C11EventHandler(), new C11EventHandler())
      		   						 .then(new C21EventHandler(), new C21EventHandler());
      disruptor.start();
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

机智的路易

用爱发电是走不远的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值