无锁的生产者–消费者
在上一篇使用BlockingQueue队列实现的生产者–消费者模式中,从BlockingQueue队列的源码可以看到两点值得注意,第一是为了保证线程安全,BlockingQueue使用了重入锁。第二,为了实现缓冲区满时,生产者等待,缓冲区空时,消费者等待,以及何时唤醒生产者/消费者的问题,使用了Condition来完成线程阻塞与唤醒。既然选择了使用锁来完成线程同步,那么在多个生产者和消费者情况下就有可能出现各种锁带来的问题,如线程等待锁而被阻塞,死锁等情况,都降低了程序整体的效率。
为了解决锁带来的性能损失,相信你会想到用无锁方式来替换BlockingQueue这个缓冲区队列,Disruptor框架就是一个使用CAS操作的内存队列,与普通的队列不同,Disruptor框架使用的是一个基于数组实现的环形队列,无论是生产者向缓冲区里提交任务,还是消费者从缓冲区里获取任务执行,都使用CAS操作。
为什么使用环形队列?
第一,简化了多线程同步的复杂度。学数据结构的时候,实现队列都要两个指针head和tail来分别指向队列的头和尾,对于一般的队列是这样,想象下,如果有多个生产者同时往缓冲区队列中提交任务,某一生产者提交新任务后,tail指针都要做修改的,那么多个生产者提交任务,头指针不会做修改,但会对tail指针产生冲突,例如某一生产者P1要做写入操作,在获得tail指针指向的对象值V后,执行compareAndSet()方法前,tail指针被另一生产者P2修改了,这时生产者P1执行compareAndSet()方法,发现tail指针指向的值V和期望值E不同,导致冲突。同样,如果多个消费者不断从缓冲区中获取任务,不会修改尾指针,但会造成队列头指针head的冲突问题(因为队列的FIFO特点,出列会从头指针出开始)。
环形队列的一个特点就是只有一个指针,只通过一个指针来实现出列和入列操作。如果使用两个指针head和tail来管理这个队列,有可能会出现“伪共享”问题(伪共享问题在下面我会详细说),因为创建队列时,head和tail指针变量常常在同一个缓存行中,多线程修改同一缓存行中的变量就容易出现伪共享问题。
第二,由于使用的是环形队列,那么队列创建时大小就被固定了,Disruptor框架中的环形队列本来也就是基于数组实现的,使用数组的话,减少了系统对内存空间管理的压力,因为它不像链表,Ja