2015年7月28日星期二

Java线程池相关-ForkJoinPool

当一个问题被拆分成两个或者多个子问题的时候,需要启动多个子线程去执行,在必要的情况下会迭代的依次启动下去。这里就产生了一些线程之间的依赖,这个大的问题需要等待它的子问题线程返回,因此需要某些机制来保证他们的同步。ThreadPoolExecutor默认使用的线程池是期望他们所有执行的任务都是不相关的,可以尽可能的并行执行。

ForkJoinPool有一个特点是work stealing。每个工作线程都有自己的工作队列,这是使用deque来实现的。当一个任务划分一个新线程时,它将自己推到 deque 的头部。当一个任务执行与另一个未完成任务的合并操作时,它会将另一个任务推到队列头部并执行,而不会休眠以等待另一任务完成(像 Thread.join() 的操作一样)。当线程的任务队列为空,它将尝试从另一个线程的 deque 的尾部 窃取另一个任务。如果我们用传统的ThreadPoolExecutor则比较难用上work stealing的技术。
Fork/Join 模式有自己的适用范围。如果一个应用能被分解成多个子任务,并且组合多个子任务的结果就能够获得最终的答案,那么这个应用就适合用 Fork/Join 模式来解决。



ForkJoinPool核心的添加是新的ForkJoinPool执行者,专门执行实现了ForkJoinTask接口的实例。ForkJoinTask对象支持创建子任务来等待子任务完成。有了这些清晰的语义,当一个任务正在等待另一个任务完成并且有待执行的任务时,executor就能够通过”偷取”任务,在内部的线程池里分发任务。

ForkJoinTask对象主要有两个重要的方法:
  • fork()方法允许ForkJoinTask任务异步执行,也允许一个新的ForkJoinTask从存在的ForkJoinTask中被启动。
  • 反过来, join()方法允许一个ForkJoinTask等待另一个ForkJoinTask执行完成。
如图所示,通过fork()和join()实现任务间的相互合作。注意fork()和join()方法名称不应该与POSIX中的进程能够复制自己的过程相混淆。fork()只会让ForkJoinPool调度一个新的任务,而不会创建子虚拟机。


有两种类型的ForkJoinTask的定义:
  • RecursiveAction的实例代表执行没有返回结果。
  • 相反,RecursiveTask会有返回值。

通常,RecursiveTask是首选的,因为大部分分而治之的算法会在数据集上计算后返回结果。对于任务的执行,不同的同步和异步选项是可选的,这样就可以实现复杂的模式。
注意点:ForkJoinTask对应的fork/join任务应该是纯内存算法,而没有I/O操作。此外,应该尽可能避免通过共享状态来进行任务间的通信,因为这通常意味着加锁会被执行。理想情况下,仅当一个任务fork另一个任务或一个任务join另一个任务时才进行任务通信。

ForkJoinPool系统介绍:http://ifeve.com/fork-and-join-java/
Java线程池概述:http://blog.csdn.net/dm_vincent/article/details/39505977
详解介绍了线程池的线程数目如何设置以及普通线程池和ForkJoinPool的区别的使用场景。

ForkJoinPool简单源码解析:http://blog.csdn.net/aesop_wubo/article/details/10300273

ForkJoinPool介绍:
http://www.ibm.com/developerworks/cn/java/j-jtp11137.html
http://www.ibm.com/developerworks/cn/java/j-lo-forkjoin/index.html

2015年7月19日星期日

J2Cache学习记录

视频介绍:http://v.youku.com/v_show/id_XNzAzMTY5MjUy.html
简单介绍:http://www.oschina.net/question/12_140335
吐槽:http://blog.csdn.net/chenleixing/article/details/44629005


问题:
为什么只使用独立的Java缓存框架Ehcache不行?
Ehcache这类缓存框架,是一个进程内的缓存框架,如果是集群的话会导致缓存数据不同步。
缓存数据不同步有两个解决办法:
1. 集中式缓存,直接将 Ehcache 换成 Redis 这类产品。如果访问量不大是没问题,访问量大,大量的缓存数据访问使得应用服务器和缓存服务器之间的网络I/O成为瓶颈。
2. Ehcache 的分布式,节点间大量的数据复制带来额外的开销,在节点多的情况下此问题越发严重。
因此,出现了两级缓存框架J2Cache,引入集中式缓存Redis,通过进程内的Ehcache 缓存来缓解网络 I/O 瓶颈。同时也降低集中式缓存服务器的压力。

拓扑结构:

数据流:
缓存同步:

源码阅读:
J2Cache使用JGroups进行组播。https://github.com/belaban/JGroups
使用FST进行对象序列化。https://github.com/RuedigerMoeller/fast-serialization

(更新中)

2015年7月15日星期三

Hive调优-控制hive中的mapper和reducer数量

一 控制hive任务中的mapper数

1. 通常情况下,作业会通过input的目录产生一个或者多个map任务。
主要的决定因素有: input的文件总个数,input的文件大小,集群设置的文件块大小(目前为128M, 可在hive中通过set dfs.block.size;命令查看到,该参数不能自定义修改);

2. 举例:
a) 假设input目录下有1个文件a,大小为780M,那么hadoop会将该文件a分隔成7个块(6个128m的块和1个12m的块),从而产生7个map数
b) 假设input目录下有3个文件a,b,c,大小分别为10m,20m,130m,那么hadoop会分隔成4个块(10m,20m,128m,2m),从而产生4个map数
即,如果文件大于块大小(128m),那么会拆分,如果小于块大小,则把该文件当成一个块。

3. 是不是map数越多越好?
答案是否定的。如果一个任务有很多小文件(远远小于块大小128m),则每个小文件也会被当做一个块,用一个map任务来完成,
而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。
而且,同时可执行的map数是受限的。
Map数越多也造成了巨大的资源浪费。

4. 是不是保证每个map处理接近128m的文件块,即map数越少越好?
答案也是否定的。比如有一个127m的文件,正常会用一个map去完成,但这个文件只有一个或者两个小字段,却有几千万的记录;如果map处理的逻辑比较复杂,用一个map任务去做,肯定也比较耗时。 
任务逻辑复杂
二 控制hive任务中的reducer数

1. Hive自己如何确定reduce数:
reduce个数的设定极大影响任务执行效率,不指定reduce个数的情况下,Hive会猜测确定一个reduce个数,基于以下两个设定: 
hive.exec.reducers.bytes.per.reducer --每个reduce任务处理的数据量,默认为1000^3=1G
hive.exec.reducers.max   --每个任务最大的reduce数,默认为999
计算reducer数的公式很简单N=min(参数2,总输入数据量/参数1)
即,如果reduce的输入(map的输出)总大小不超过1G,那么只会有一个reduce任务;

2. 调整reducer的个数
方法一:调整hive.exec.reducers.bytes.per.reducer参数的值;
set hive.exec.reducers.bytes.per.reducer=500000000;   --减少每个reducer处理的数据量
方法二;
set mapred.reduce.tasks = 15;
3. reducer个数并不是越多越好;
启动和初始化reduce也会消耗时间和资源; 另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;

4. 只有一个reduce的情况; 
只有一个reduce任务的情况,除了数据量小于hive.exec.reducers.bytes.per.reducer参数值的情况外,还有以下原因:
a) 没有group by的汇总,比如把select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt; 写成 select count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04';
b) Order by 和 笛卡尔积
因为这些操作都是全局的,所以hadoop不得不用一个reduce去完成;

hive优化使用技巧

参考:http://itindex.net/detail/53619-hive-优化

Hive是将符合SQL语法的字符串解析生成可以在Hadoop上执行的MapReduce的工具。使用Hive尽量按照分布式计算的一些特点来设计sql,和传统关系型数据库有区别,所以需要去掉原有关系型数据库下开发的一些固有思维。

基本原则:

1:尽量尽早地过滤数据,减少每个阶段的数据量,对于分区表要加分区,同时只选择需要使用到的字段。
join时,对于where条件,可以使用子查询设计join。
例如:
select .. from (select .. from ..) A join (select .. from ..) B on ..

2:尽量原子化操作,尽量避免一个SQL包含复杂逻辑,可以使用中间表来完成复杂的逻辑.
3:单个SQL所起的JOB个数尽量控制在5个以下.

4:慎重使用mapjoin,一般行数小于2000行,大小小于1M(扩容后可以适当放大)的表才能使用,小表要注意放在join的左边,否则会引起磁盘和内存的大量消耗.

5:写SQL要先了解数据本身的特点,如果有join ,group操作的话,要注意是否会有数据倾斜,如果出现数据倾斜,应当做如下处理:

set hive.exec.reducers.max=200;
set mapred.reduce.tasks= 200;---增大Reduce个数
set hive.groupby.mapaggr.checkinterval=100000 ;--这个是group的键对应的记录条数超过这个值则会进行分拆,值根据具体数据量设置
set hive.groupby.skewindata=true; --如果是group by过程出现倾斜 应该设置为true
set hive.skewjoin.key=100000; --这个是join的键对应的记录条数超过这个值则会进行分拆,值根据具体数据量设置
set hive.optimize.skewjoin=true;--如果是join 过程出现倾斜 应该设置为true
6:如果union all的部分个数大于2,或者每个union部分数据量大,应该拆成多个insert into 语句,实际测试过程中,执行时间能提升50%.
insert into table ..
select .. from (
   select .. from A
    union all 
   select .. from B
    union all 
   select .. from C
)
改为:
insert into table ..
select .. from A

insert into table ..
select .. from B

insert into table ..
select .. from C
7: 如何合并小文件,减少map数?
如果一个表中的map数特别多,可能是由于文件个数特别多,而且文件特别小照成的,可以进行如下操作,合并文件:
set mapred.max.split.size=100000000; // 100M
set mapred.min.split.size.per.node=100000000;
set mapred.min.split.size.per.rack=100000000;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; // 合并小文件

但是,当map的业务逻辑很复杂时,需要释放增加map数。可以将table进行随机分布,使用新的table代替原表。
set mapred.reduce.tasks=10; 
create table temp as  
select * from a  
distribute by rand(123);
8: hive如何确定reduce数, reduce的个数基于以下参数设定:
hive.exec.reducers.bytes.per.reducer(每个reduce任务处理的数据量,默认为1000^3=1G)
hive.exec.reducers.max(每个任务最大的reduce数,默认为999)
计算reducer数的公式很简单N=min(参数2,总输入数据量/参数1)
即,如果reduce的输入(map的输出)总大小不超过1G,那么只会有一个reduce任务;所以调整以下参数:
set hive.exec.reducers.bytes.per.reducer=500000000; (500M)
set mapred.reduce.tasks = 15;

9:  Count(distinct)
当count distinct 的记录非常多的时候,设置以下两个参数:
hive.map.aggr = true
set hive.groupby.skewindata=true;

10: Group by
Group By的方法是在reduce做一些操作,这样会导致两个问题:
map端聚合,提前一部分计算:hive.map.aggr = true,
同时设置间隔:hive.groupby.mapaggr.checkinterval
均衡处理:hive.groupby.skewindata
这是针对数据倾斜的,设为ture的时候,任务的reduce会把原来一个job拆分成两个,第一个的job中reduce处理处理不同的随即分发过来的key的数据,生成中间结果,再由最后一个综合处理。

11: Order by, Sort by ,Dristribute by,Cluster By
order by VS Sort by: order by是在全局的排序,只用一个reduce去跑,所以在set hive.mapred.mode=strict 模式下,order by 必须limit,否则报错。Sort by只保证同一个reduce下排序正确。
Distribute by with sort by: Distribute by 是按指定的列把map 输出结果分配到reduce里。所以经常和sort by 来实现对某一字段的相同值分配到同一个reduce排序。
Cluster by 实现了Distribute by+ sort by 的功能.

12: 合并MapReduce操作
Multi-group by
Multi-group by是Hive的一个非常好的特性,它使得Hive中利用中间结果变得非常方便。例如:
FROM (SELECT a.status, b.school, b.gender

FROM status_updates a JOIN profiles b

ON (a.userid = b.userid and

a.ds='2009-03-20' )

) subq1

INSERT OVERWRITE TABLE gender_summary

PARTITION(ds='2009-03-20')

SELECT subq1.gender, COUNT(1) GROUP BY subq1.gender

INSERT OVERWRITE TABLE school_summary

PARTITION(ds='2009-03-20')

SELECT subq1.school, COUNT(1) GROUP BY subq1.school
上述查询语句使用了Multi-group by特性连续group by了2次数据,使用不同的group by key。这一特性可以减少一次MapReduce操作。

13: 参数调整
http://itindex.net/detail/53620-hive-%E4%BC%98%E5%8C%96

2015年7月14日星期二

大数据差异比较

问题:
有两份数据A和B,要求求解A和B的内容差异?
前提条件:
1. A和B使用文件保存,且数据量在G级别;
2. A和B的文件内容是结构化的;
3. A和B的文件内容是乱序的,即A的第一行可能在B中,但是在第n行,也可能不存在,B也一样。

解决方案:

1.linux diff命令

缺点:diff对文件进行按序比较,比如:
a.txt:
1 a
2 b
4 d
3 c
b.txt
2 b
4 d
3 c
1 a
5 e
执行命令:diff a.txt b.txt
1d0
< 1 a
4a4,5
> 1 a
> 5 e
分析结果可知,A和B同时包含的数据依然出现在结果中,需要对结果进行额外处理。当数据量较大时,diff运行超级慢。

2 分桶+grep
描述:A和B的数据是结构化的,因此可以取一列进行hash,将A和B进行分桶,比如分桶至128个文件中,然后分别比对A(1-128)和B(1-128)。
比对时,使用grep命令逐行扫两个文件即可。分桶可以使得grep的文件规模降低。
缺点:这是一个很笨的方法,速度及其慢。我采用了这个方法。呵呵!

3 sort+comm

sort命令是帮我们依据不同的数据类型进行排序,其语法及常用参数格式:
  sort [-bcfMnrtk][源文件][-o 输出文件]
补充说明:sort可针对文本文件的内容,以行为单位来排序。

参  数:
-b 忽略每行前面开始出的空格字符。
-c 检查文件是否已经按照顺序排序。
-C会检查文件是否已排好序,如果乱序,不输出内容,仅返回1。
-f 排序时,忽略大小写字母。
-M 将前面3个字母依照月份的缩写进行排序。
-n 依照数值的大小排序,防止10比2小的情况。
-o<输出文件> 将排序后的结果存入指定的文件。
-r 以相反的顺序来排序。
-t<分隔字符> 指定排序时所用的栏位分隔字符。
-k 选择以哪个区间进行排序。
-u 在输出行中去除重复行

例如,对a.txt进行排序:sort -n -k 1 -t ' ' a.txt

comm命令——对已经有序的文件进行比较
comm对文件进行处理时,要求文件已经有序,如果没有顺序,请使用sort进行排序后进行处理。语法:
comm [-123][--help][--version][第1个文件][第2个文件]

补充说明:
这项指令会一列列地比较两个已排序文件的差异,并将其结果显示出来,如果没有指定任何参数,则会把结果分成3行显示:
第1行仅是在第1个 文件中出现过的列;
第2行是仅在第2个文件中出现过的列;
第3行则是在第1与第2个文件里都出现过的列。

若给予的文件名称为"-",则comm指令会从标 准输入设备读取数据。
参数:
-1 不显示只在第1个文件里出现过的列。
-2 不显示只在第2个文件里出现过的列。
-3 不显示同时在第1和第2个文件里出现过的列。
例如:
comm a.txt b.txt 
               1 a
                2 b
                3 c
                4 d
        5 e
缺点:对于大文件,依然很慢。

4 egrep
egrep -f a.txt -v b.txt

5 使用hadoop和hive
最终采取的方案。
因为A和B都是结构化数据,因此可以使用hadoop和hive。创建两张表,将A和B分别载入,然后求内容差集。

使用hive的 关键字left semi join,解决的问题是:IN/EXISTS,即求并集。
例如:
select test_1.id, test_1.num from test_1 left semi join test_2 on (test_1.id = test_2.id);

使用hive的关键字left outer join, 解决A差B的问题:
例如:
select test_1.id, test_1.num from test_1 left outer join test_2 on (test_2.id = test_2.id) where test_2.num is null;

hive命令行配置条件

set mapred.job.priority=HIGH;
set mapred.job.groups=ods;
set mapred.job.queue.name=ods;
set mapred.map.tasks.speculative.execution = false;
set mapred.reduce.tasks.speculative.execution = false;
set mapred.job.map.capacity=1000;
set mapred.job.reduce.capacity=500;
set hive.metastore.client.socket.timeout=100000;
set hive.groupby.skewindata=true;
add jar /home/work/metastore.online/hive/lib/nova-pb-schema-1.0.jar;
add jar /home/work/metastore.online/hive/lib/hive-serde-2.3.33.jar;
add jar /home/work/metastore.online/hive/lib/hive-contrib-2.3.33.jar;

文件描述符

参考:http://www.bottomupcs.com/file_descriptors.html
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的 信息,称为进程描述符(Process Descriptor),而在操作系统理论中称为进程控制块 (PCB,Process Control Block)。task_struct中有一个指针(struct files_struct *files; )指向files_struct结构体,称为文件 描述符表,其中每个表项包含一个指向已打开的文件的指针。

用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引 (即0、1、2、3这些数字),这些索引就称为文件描述符(File Descriptor),用int 型变量保存。 当调用open 打开一个文件或创建一个新文件时,内核分配一个文件描述符并返回给用户程序,该文件描述符表项中的指针指向新打开的文件。当读写文件时,用户程序把文件描述符传给read 或write ,内核根据文件描述符找到相应的表项,再通过表项中的指针找到相应的文件。
Java对应的文件描述符类为:FileDescriptor
使用Demo:
// 新建FileInputStream对象
File file = new File(FileName);
FileInputStream in1 = new FileInputStream(file);
// 获取文件“file.txt”对应的“文件描述符”
FileDescriptor fdin = in2.getFD();
// 根据“文件描述符”创建“FileInputStream”对象
 FileInputStream in3 = new FileInputStream(fdin);

2015年7月12日星期日

shell学习笔记-输入输出,重复执行命令,字段分隔符,比较与测试

一 输入输出
子shell通过()操作符进行定义,子shell的改变不会影响主shell
ps:通过引用子shell的方式保留空格和换行符时,需要使用双引号 "(..)"
#管道
cmd1 | cmd2   #e.g.   ls | cat -n >out.txt
#子shell
cmd_output=$(ls | cat -n)
#反引用
cmd_output=`cmd`
二 运行命令直至执行成功

repeat(){while :; do &@ && return;  sleep 20; done } 
#&@表示传入的命令  例如
repeat wget -c http://....
true作为二进制文件实现,因此使用:命令,:命令默认返回为0的退出码

三 字段分隔符和迭代器

内部字段分隔符为IFS
data="one,two,three,four"
old_ifs=$IFS
IFS=,
for item in $data;
do
        echo $item
done
IFS=$old_ifs
四 比较与测试
-a逻辑与    condition1 -a condition2 
if [[  condition1 ]] && [[ condition2 ]]
-o逻辑或    condition1 -a condition2  
if [[  condition1 ]] || [[ condition2 ]]

shell学习笔记-文件描述符,数组,别名,调试

由于工作中需要写shell,因此进行系统性学习,并记录学习笔记。使用的学习资料为《Linux Shell脚本攻略》第二版。

一 文件描述符

 自定义文件描述符:

exec 3<input.txt #使用文件进行文件描述符输入
echo "string" >&4 #写入文件描述符4
cat<&3  #读取文件描述符3
二 数组和关联数组(Map)

数组: 
#定义
arr=(1 2 3 4)

echo "all:"
#打印所有元素
echo ${arr[*]}

echo ${arr[@]}
#打印长度
echo "length:" ${#arr[*]}
关联数组:
#deifination
declare -A ass_arr
#assignment
ass_arr=(['apple']=10 ['pear']=20)
echo ${ass_arr['apple']}
#get all keys
echo ${!ass_arr[*]}
三 别名
alias cmd='new cmd'
使用\cmd 进行转义,可以忽略别名

四 调试
#全局调试
sh  -x script 或者 #!/bin/bash -vx
#局部调试
set -x
cmd
set +x

2015年7月10日星期五

Disruptor详解

对Disruptor的最初印象就是ringbuffer。但是尽管ringbuffer是整个模式(Disruptor)的核心,但是Disruptor对ringbuffer的访问控制策略才是真正的关键点所在。
  • ringbuffer到底是什么?
正如名字所说的一样,它是一个环(首尾相接的环),你可以把它用做在不同上下文(线程)间传递数据的buffer。




基本来说,ringbuffer拥有一个序号,这个序号指向数组中下一个可用的元素。(校对注:如下图右边的图片表示序号,这个序号指向数组的索引4的位置。)



随着你不停地填充这个buffer(可能也会有相应的读取),这个序号会一直增长,直到绕过这个环。



要找到数组中当前序号指向的元素,可以通过mod操作:

sequence mod array length = array index

以上面的ringbuffer为例(java的mod语法):12 % 10 = 2。

事实上,上图中的ringbuffer只有10个槽完全是个意外。如果槽的个数是2的N次方更有利于基于二进制的计算机进行计算。

(校对注:2的N次方换成二进制就是1000,100,10,1这样的数字, sequence & (array length-1) = array index,比如一共有8槽,3&(8-1)=3,HashMap就是用这个方式来定位数组元素的,这种方式比取模的速度更快。)
那又怎么样?

如果你看了维基百科里面的关于环形buffer的词条,你就会发现,ringbuffer的实现方式,与其最大的区别在于:没有尾指针。ringbuffer只维护了一个指向下一个可用位置的序号。这种实现是经过深思熟虑的—ringbuffer选择用环形buffer的最初原因就是想要提供可靠的消息传递。我们需要将已经被服务发送过的消息保存起来,这样当另外一个服务通过nak (校对注:拒绝应答信号)告诉ringbuffer没有成功收到消息时,ringbuffer能够重新发送给他们。

听起来,环形buffer非常适合这个场景。它维护了一个指向尾部的序号,当收到nak(校对注:拒绝应答信号)请求,可以重发从那一点到当前序号之间的所有消息:



ring buffer和大家常用的队列之间的区别是,ringbuffer不删除buffer中的数据,也就是说这些数据一直存放在buffer中,直到新的数据覆盖他们。这就是和维基百科版本相比,ringbuffer不需要尾指针的原因。ringbuffer本身并不控制是否需要重叠(决定是否重叠是生产者-消费者行为模式的一部分
  • 它为什么如此优秀?
之所以ringbuffer采用这种数据结构,是因为它在可靠消息传递方面有很好的性能。这就够了,不过它还有一些其他的优点。

首先,因为它是数组,所以要比链表快,而且有一个容易预测的访问模式。(译者注:数组内元素的内存地址的连续性存储的)。这是对CPU缓存友好的—也就是说,在硬件级别,数组中的元素是会被预加载的,因此在ringbuffer当中,cpu无需时不时去主存加载数组中的下一个元素。(校对注:因为只要一个元素被加载到缓存行,其他相邻的几个元素也会被加载进同一个缓存行)

其次,你可以为数组预先分配内存,使得数组对象一直存在(除非程序终止)。这就意味着不需要花大量的时间用于垃圾回收。此外,不像链表那样,需要为每一个添加到其上面的对象创造节点对象—对应的,当删除节点时,需要执行相应的内存清理操作。

  • ringbuffer的为什么这么快?--cache line padding
设想你的long类型的数据不是数组的一部分。设想它只是一个单独的变量。让我们称它为head,这么称呼它其实没有什么原因。然后再设想在你的类中有另一个变量紧挨着它。让我们直接称它为tail。现在,当你加载head到缓存的时候,你也免费加载了tail。

听想来不错。直到你意识到tail正在被你的生产者写入,而head正在被你的消费者写入。这两个变量实际上并不是密切相关的,而事实上却要被两个不同内核中运行的线程所使用。
设想你的消费者更新了head的值。缓存中的值和内存中的值都被更新了,而其他所有存储head的缓存行都会都会失效,因为其它缓存中head不是最新值了。

现在如果一些正在其他内核中运行的进程只是想读tail的值,整个缓存行需要从主内存重新读取。那么一个和你的消费者无关的线程读一个和head无关的值,它被缓存未命中给拖慢了。

当然如果两个独立的线程同时写两个不同的值会更糟。因为每次线程对缓存行进行写操作时,每个内核都要把另一个内核上的缓存块无效掉并重新读取里面的数据。你基本上是遇到两个线程之间的写冲突了,尽管它们写入的是不同的变量。

这叫作“伪共享”(译注:可以理解为错误的共享),因为每次你访问head你也会得到tail,而且每次你访问tail,你也会得到head。这一切都在后台发生,并且没有任何编译警告会告诉你,你正在写一个并发访问效率很低的代码。

解决方案-神奇的缓存行填充

你会看到Disruptor消除这个问题,至少对于缓存行大小是64字节或更少的处理器架构来说是这样的(译注:有可能处理器的缓存行是128字节,那么使用64字节填充还是会存在伪共享问题),通过增加补全来确保ring buffer的序列号不会和其他东西同时存在于一个缓存行中。
1public long p1, p2, p3, p4, p5, p6, p7; // cache line padding
2    private volatile long cursor = INITIAL_CURSOR_VALUE;
3    public long p8, p9, p10, p11, p12, p13, p14; // cache line padding


因此没有伪共享,就没有和其它任何变量的意外冲突,没有不必要的缓存未命中。
在你的Entry类中也值得这样做,如果你有不同的消费者往不同的字段写入,你需要确保各个字段间不会出现伪共享。

整理自:http://ifeve.com/disruptor-writing-ringbuffer/