内存可见性和伪共享问题

内存可见性和伪共享问题

文章目录

什么是内存可见性问题

为什么会出现可见性问题

解决可见性问题的方法

[1. 使用volatile关键字](#1. 使用volatile关键字)

[2. 使用synchronized](#2. 使用synchronized)

[3. 使用java.util.concurrent包下的原子类](#3. 使用java.util.concurrent包下的原子类)

什么是伪共享问题

CPU缓存行

伪共享的危害

解决伪共享的方法

[1. 缓存行填充](#1. 缓存行填充)

[2. 使用@Contended注解(JDK 8+)](#2. 使用@Contended注解(JDK 8+))

什么是内存可见性问题

内存可见性问题指的是:一个线程对共享变量的修改,对其他线程不一定立即可见。这听起来很奇怪,但在现代多核处理器架构下,确实存在这个问题。

为什么会出现可见性问题

现代CPU为了提升性能,每个核心都有自己的缓存,多个核心共享L3缓存。当线程修改变量时,可能只是修改了CPU缓存中的副本,而没有立即写回主内存。

java

复制代码

public class VisibilityExample {

private boolean flag = false;

private int counter = 0;

public void writer() {

counter = 1; // 写操作1

flag = true; // 写操作2

}

public void reader() {

if (flag) { // 读操作1

System.out.println(counter); // 读操作2

}

}

}

在上面的例子中,即使flag为true,counter的值对读线程可能仍然不可见。

解决可见性问题的方法

1. 使用volatile关键字

java

复制代码

public class VolatileExample {

private volatile boolean flag = false;

private volatile int counter = 0;

// volatile确保修改立即对其他线程可见

}

2. 使用synchronized

java

复制代码

public class SynchronizedExample {

private boolean flag = false;

private int counter = 0;

public synchronized void writer() {

counter = 1;

flag = true;

}

public synchronized void reader() {

if (flag) {

System.out.println(counter);

}

}

}

3. 使用java.util.concurrent包下的原子类

java

复制代码

public class AtomicExample {

private final AtomicBoolean flag = new AtomicBoolean(false);

private final AtomicInteger counter = new AtomicInteger(0);

public void writer() {

counter.set(1);

flag.set(true);

}

public void reader() {

if (flag.get()) {

System.out.println(counter.get());

}

}

}

什么是伪共享问题

伪共享是指多个线程访问同一缓存行中的不同变量时,即使这些变量在逻辑上是独立的,但由于它们位于同一缓存行,一个线程的修改会导致其他线程的缓存行失效,从而引起不必要的缓存同步开销。

CPU缓存行

现代CPU的缓存是以缓存行为单位进行管理的,一般大小为64字节,相当于8个long类型的数据。当CPU访问内存时,会将包含目标地址的整个缓存行加载到缓存中。

伪共享的危害

java

复制代码

public class FalseSharingExample {

// 这两个变量很可能在同一缓存行中

private volatile long variableA = 0L;

private volatile long variableB = 0L;

public void threadA() {

for (int i = 0; i < 100000000; i++) {

variableA++; // 线程A修改variableA

}

}

public void threadB() {

for (int i = 0; i < 100000000; i++) {

variableB++; // 线程B修改variableB

}

}

}

在上面的例子中,虽然两个线程操作不同的变量,但由于变量可能在同一缓存行,每次修改都会导致对方的缓存行失效,导致线程读取变量的时候又要去内存中读取,性能大幅下降。

解决伪共享的方法

1. 缓存行填充

java

复制代码

public class PaddingExample {

private volatile long variableA = 0L;

// 使用填充字节避免伪共享

private long p1, p2, p3, p4, p5, p6, p7;

private volatile long variableB = 0L;

private long p8, p9, p10, p11, p12, p13, p14;

}

2. 使用@Contended注解(JDK 8+)

java

复制代码

@sun.misc.Contended

public class ContendedExample {

private volatile long variableA = 0L;

@sun.misc.Contended

private volatile long variableB = 0L;

}

使用@Contended需要JVM参数:-XX:-RestrictContended

相关数据

365bet体育在线官 杓字的意思、解释和含义以及拼音、笔画和笔顺
365bet体育在线官 买三星美版十大忠告 美版三星s25的优缺点
365bet体育在线官 老年肌肉减少症的认知与研究的最新进展