声明:关于概念相关的部分,本文不会着重讲述,请参阅列出的参考文档
一、什么是伪共享
- 什么是缓存行
众所周知,CPU有缓存,CPU会从主存中读取数据到缓存中,而读取的单元大小便是一个缓存行大小,如果两个数据在同一个读取单元上,则他们会被读取到CPU的同一个缓存行上。同理,将缓存数据刷新到主存也是同样的道理。总之,缓存行就是这么一个单元化的东西。 - 缓存行与伪共享的关系
如果两个变量存在于内存的同一个缓存行上,这时A线程需要对内存上的A变量进行操作,而B线程要对内存上的B变量进行操作,不管谁先操作,谁都要先对该部分内存区域进行锁定(如果不这样,假设A先一步将操作结果刷新到主存,而B后一步刷新到主存,B只修改了变量B,A变量还是原来的,这样当整个缓存行刷回去时就会把A线程所做的操作全部覆盖掉了),锁定了之后,B只能够等待A把缓存内容刷新回去了再进行操作,这就浪费了大量时间。至于为什么把这种现象称为“伪共享”,是因为A和B变量表面上看似乎是独立操作的,应该不存在并发问题,但是仅仅因为他们共同存在于一个缓存行,就造成了和普通并发一样的问题(即需要锁来控制操作顺序)。
二、一个发生了伪共享的代码示例
该段代码将会创建两个线程,两个线程将会同时操作一个对象,只不过t1线程操作对象的v1域,而t2线程操作对象的v2域,两个操作都将执行10亿次。
- Target
public class Target implements BaseTarget{
private volatile long v1;
private volatile long v2;
@Override
public void setV1(long v1) {
this.v1 = v1;
}
@Override
public void setV2(long v2) {
this.v2 = v2;
}
}
- Main
public class Main {
//...
public static void main(String[] args) throws InterruptedException {
falseSharingTest(false);
}
public static void falseSharingTest(boolean optimize){
int times = 1000000000;
BaseTarget target = null;
if(optimize){
target = new BetterTarget();
}else{
target = new Target();
}
final BaseTarget finalTarget = target;
//我的机器是4核的所以可以这么写
fixedThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 1);
fixedThreadPool.submit(() -> {
for(int i =0; i < times ; i++){
finalTarget.setV1(i);
}
});
fixedThreadPool.submit(() -> {
for(int i =0; i < times ; i++){
finalTarget.setV2(i);
}
});
long start = System.nanoTime();
threadPoolShutdown();
System.out.println(Duration.ofNanos(System.nanoTime() - start));
}
//...
}
- 执行结果
PT4.5802031S
三、解决方案及示例
- 解决方案
Java8之前可以使用votile的变量进行填充,直到把目标变量挤入下一个缓存行。
Java8则可以使用@Contended注解,该注解会帮你做好一切事情,对了如果要使其生效的话需要在虚拟机参数中添加下列参数:
-XX:-RestrictContended
- BetterTarget
public class BetterTarget implements BaseTarget{
private volatile long v1;
@Contended
private volatile long v2;
@Override
public void setV1(long v1) {
this.v1 = v1;
}
@Override
public void setV2(long v2) {
this.v2 = v2;
}
}
- Main
public class Main {
public static void main(String[] args) throws InterruptedException {
falseSharingTest(true);
}
public static void falseSharingTest(boolean optimize) throws InterruptedException {
//略
}
}
- 执行结果
PT1.0964604S
一个小小的注解使得性能足足相差了4倍!!!
- 额外
如果你发现Contended类无法被导入,你可以试试这个解决办法
参考文档:
[1] 伪共享(false sharing),并发编程无声的性能杀手
网友评论