java8中@Contended注解的使用
作者:一只爱撸猫的程序猿
@Contended
是Java 8中引入的一个注解,用于减少多线程环境下的“伪共享”现象,以提高程序的性能。
要理解@Contended
的作用,首先要了解一下什么是伪共享(False Sharing)。
1. 什么是伪共享?
伪共享(False Sharing)是多线程环境中的一种现象,涉及到CPU的缓存机制和缓存行(Cache Line)。
现代CPU中,为了提高访问效率,通常会在CPU内部设计一种快速存储区域,称为缓存(Cache)。CPU在读写主内存中的数据时,会首先查看该数据是否已经在缓存中。如果在,就直接从缓存读取,避免了访问主内存的耗时;如果不在,则从主内存读取数据并放入缓存,以便下次访问。
缓存不是直接对单个字节进行操作的,而是以块(通常称为“缓存行”)为单位操作的。一个缓存行通常包含64字节的数据。
在多线程环境下,如果两个或更多的线程在同一时刻分别修改存储在同一缓存行的不同数据,那么CPU为了保证数据一致性,会使得其他线程必须等待一个线程修改完数据并写回主内存后,才能读取或者修改这个缓存行的数据。尽管这些线程可能实际上操作的是不同的变量,但由于它们位于同一缓存行,因此它们之间就会存在不必要的数据竞争,这就是伪共享。
伪共享会降低并发程序的性能,因为它会增加缓存的同步操作和主内存的访问。解决伪共享的一种方式是尽量让经常被并发访问的变量分布在不同的缓存行中,例如,可以通过增加无关的填充数据,或者利用诸如Java的@Contended
注解等工具。
2. @Contended注解是什么?
@Contended
是Java 8引入的一个注解,设计用于减少多线程环境下的伪共享(False Sharing)问题以提高程序性能。
伪共享是现代多核处理器中一个重要的性能瓶颈,它发生在多个处理器修改同一缓存行(Cache Line)中的不同数据时。缓存行是内存的基本单位,一般为64字节。当一个处理器读取主内存中的数据时,它会将整个缓存行(包含需要的数据)加载到本地缓存(L1,L2或L3缓存)中。如果另一个处理器修改了同一缓存行中的其他数据,那么原先加载到缓存中的数据就会变得无效,需要重新从主内存中加载。这会增加内存访问的延迟,降低程序性能。
@Contended
注解可以标注在字段或者类上。它能使得被标注的字段在内存布局上尽可能地远离其他字段,使得被标注的字段或者类中的字段分布在不同的缓存行上,从而减少伪共享的发生。
例如,考虑以下代码:
public class Foo { @Contended long x; long y; }
在这里,x
被@Contended
注解标记,所以x
和y
可能会被分布在不同的缓存行上,这样如果多个线程并发访问x
和y
,就不会引发伪共享。
需要注意的是,@Contended
是JDK的内部API,它在Java 8中引入,但在默认情况下是不开放的,要使用需要添加JVM参数-XX:-RestrictContended
,并且在编译时需要使用--add-exports java.base/jdk.internal.vm.annotation=ALL-UNNAMED
。此外,过度使用@Contended
可能会浪费内存,因为它会导致大量的内存空间被用作填充以保持字段间的距离。所以在使用时需要谨慎权衡内存和性能的考虑。
3. 简单案例
在Java 8及以上版本中,@Contended
注解是属于jdk的内部API,因此在正常情况下使用时需要打开开关-XX:-RestrictContended
才能正常使用。同时需要注意的是,@Contended
在JDK 9以后的版本中可能无法正常工作,因为JDK 9开始禁止使用Sun的内部API。
以下是一个@Contended
注解的简单使用案例:
import jdk.internal.vm.annotation.Contended; public class ContendedExample { @Contended volatile long value1 = 0L; @Contended volatile long value2 = 0L; public void increaseValue1() { value1++; } public void increaseValue2() { value2++; } public static void main(String[] args) { ContendedExample example = new ContendedExample(); Thread thread1 = new Thread(() -> { for (int i = 0; i < 1000000; i++) { example.increaseValue1(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 1000000; i++) { example.increaseValue2(); } }); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("value1: " + example.value1); System.out.println("value2: " + example.value2); } }
这个例子中定义了两个使用了@Contended
注解的volatile长整型字段value1
和value2
。两个线程分别对这两个字段进行增加操作。因为这两个字段使用了@Contended
注解,所以他们会被分布在不同的缓存行中,减少了因伪共享带来的性能问题。但由于伪共享的影响在实际运行中并不容易直接观察,所以这个例子主要展示了@Contended
注解的使用方式,而不是实际效果。
到此这篇关于java8中@Contended注解的使用的文章就介绍到这了,更多相关java @Contended注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!