文章目錄
- 什么是內存可見性問題
- 為什么會出現可見性問題
- 解決可見性問題的方法
- 1. 使用volatile關鍵字
- 2. 使用synchronized
- 3. 使用java.util.concurrent包下的原子類
- 什么是偽共享問題
- CPU緩存行
- 偽共享的危害
- 解決偽共享的方法
- 1. 緩存行填充
- 2. 使用@Contended注解(JDK 8+)
什么是內存可見性問題
內存可見性問題指的是:一個線程對共享變量的修改,對其他線程不一定立即可見。這聽起來很奇怪,但在現代多核處理器架構下,確實存在這個問題。
為什么會出現可見性問題
現代CPU為了提升性能,每個核心都有自己的緩存,多個核心共享L3緩存。當線程修改變量時,可能只是修改了CPU緩存中的副本,而沒有立即寫回主內存。
public class VisibilityExample {private boolean flag = false;private int counter = 0;public void writer() {counter = 1; // 寫操作1flag = true; // 寫操作2}public void reader() {if (flag) { // 讀操作1System.out.println(counter); // 讀操作2}}
}
在上面的例子中,即使flag
為true,counter
的值對讀線程可能仍然不可見。
解決可見性問題的方法
1. 使用volatile關鍵字
public class VolatileExample {private volatile boolean flag = false;private volatile int counter = 0;// volatile確保修改立即對其他線程可見
}
2. 使用synchronized
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包下的原子類
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訪問內存時,會將包含目標地址的整個緩存行加載到緩存中。
偽共享的危害
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. 緩存行填充
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+)
@sun.misc.Contended
public class ContendedExample {private volatile long variableA = 0L;@sun.misc.Contendedprivate volatile long variableB = 0L;
}
使用@Contended需要JVM參數:-XX:-RestrictContended