项目概况

性能优化之@Contended减少伪共享

2026-01-292 min read后端技术
Java进阶

一、什么叫伪共享

说到伪共享,就要说CPU缓存,我们程序执行时候信息会被保存到CPU缓存中 而这些缓存中的数据可能被多线程访问,假如一个线程还没处理完,另外一个线程 就对数据进行了修改,就会导致上一个线程发生幻读的情况,比如刚才看到a=1,然后准备a = a+1。 但是还没做,另外一个线程就先将a变成2了。导致了上一个线程计算后本来应该是a = 1 + 1,变成了a = 2 + 1 计算结果就不对了。

那么对于这种情况当然是不允许发生的,解决方案就是当发现另外一个线程更新了共享变量,就会把cpu缓存中的数据给失效。 然后都重新读取最新的变量值。

==这里有一个前提是共享变量,因为两个线程都会用到a,所以a是共享变量。==

那么我们在聊伪共享就简单了,下面举一个伪共享变量的例子。

java
1public class ContendedTest { 2 3 volatile long a; 4 5 volatile long b; 6 7 @Test 8 public void test() throws Exception { 9 ContendedTest c = new ContendedTest(); 10 Thread thread1 = new Thread(() -> { 11 for (int i = 0; i < 10000_0000L; i++) { 12 c.a = i; 13 } 14 }); 15 Thread thread2 = new Thread(() -> { 16 for (int i = 0; i < 10000_0000L; i++) { 17 c.b = i; 18 } 19 }); 20 final long start = System.nanoTime(); 21 thread1.start(); 22 thread2.start(); 23 thread1.join(); 24 thread2.join(); 25 // 1933 26 System.out.println((System.nanoTime() - start) / 100_0000); 27 } 28 29}

两个线程分别来更新a和b属性,根据缓存失效的原理,因为a和b都在同一个对象中,当一个属性被更新,就会触发cpu缓存失效。 那么等于这种情况cpu缓存就没什么用了。我们思考下两个线程分别更新a和b,而a和b没有任何关系。那么a和b是共享变量吗? 当然不是,这就叫伪共享。

二、主动告诉程序伪共享

我们可以使用 @Contended 来声明伪共享变量,从而是cpu不更新缓存。 本地测试时候记得加上jvm参数 ==-XX:-RestrictContended==,否则无效哦。

java
1public class ContendedTest { 2 3 @Contended 4 volatile int a; 5 6 @Contended 7 volatile int b; 8 9 @Test 10 public void test() throws Exception { 11 ContendedTest c = new ContendedTest(); 12 Thread thread1 = new Thread(() -> { 13 for (int i = 0; i < 10000_0000L; i++) { 14 c.a = i; 15 } 16 }); 17 Thread thread2 = new Thread(() -> { 18 for (int i = 0; i < 10000_0000L; i++) { 19 c.b = i; 20 } 21 }); 22 final long start = System.nanoTime(); 23 thread1.start(); 24 thread2.start(); 25 thread1.join(); 26 thread2.join(); 27 System.out.println((System.nanoTime() - start) / 100_0000); 28 } 29}

那么你猜下性能能提高多少呢? 前者1933后者758ms,差不多2.5倍的样子。

那么留下一个问题? 有多少场景都在使用@Contended呢? 知道的请留言评论。