Java并发编程基础-----volatile和伪共享

synchronized是Java提供的一种原子性的内置锁,其他对象可以将其作为一个同步锁使用,synchronized能够保证内存可见性和原子性,是一个重量级的锁,而有些时候只需要保证内存可见性即可的情况下,可以使用volatile关键字,用以保证变量的内存可见性。

1 volatile适用的情况

  • 写入变量值不依赖、变量的当前值时。因为如果依赖当前值,将是获取一计算一写入 三步操作,这三步操作不是原子性的,而volatile不保证原子性。
  • 读写变量值时没有加锁。因为加锁本身已经保证了内存可见性,这时候不需要把变量声明为volatile 的。

2 volatile原理
volatile可以确保对一个变量的更新对其他线程马上可见。当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方(工作内存),而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。
而这里提及的工作内存和主内存与JVM内存模型中对应关系是怎样的呢,主内存就是jvm中的堆内存,而工作内存其实就是jvm中的线程私有的jvm栈内存。

volatile只能修饰变量,且只能保证内存可见性,不能像synchronized可以修饰方法、代码块、类,也不能保证原子性。

伪共享:cpu一个缓冲行中缓存多个变量,当多个线程同时修改一个缓存行里面的多个变量时,由于同时只能有一个线程操作缓存行,所以相比将每个变量放到一个缓存行,性能会有所下降,这就是伪共享。
在java8之前解决办法:通过字节填充的方式来避免该问题,也就是创建一个变量时使
用填充字段填充该变量所在的缓存行,这样就避免了将多个变量存放在同一个缓存行中,例如如下代码。

public final static  class FilledLong{
	public volatile long value = 0L;
	public long p1, p2, p3, p4, p5, p6;
}

假如缓存行为64 宇节,那么我们在FilledLong 类里面填充了6 个long 类型的变量, 每个long 类型变量占用8字节, 加上value变量的8字节总共56 字节,另外这里FilledLong是一个类对象, 而类对象的字节码的对象头占用8字节,所以一个FilledLong对象实际会占用64字节的内存,这正好可以放入一个缓存行。

JDK 8 提供了一个sun.misc . Contended 注解,用来解决伪共享问题;需要注意的是, 在默认情况下,@Contended 注解只用于Java 核心类, 比如此包下的类。如果用户类路径下的类需要使用这个注解, 则需要添加NM 参数:-XX:-RestrictContended。填充的宽度默认为128 ,要自定义宽度则可以设置-XX : Con nd巳dPaddingWidth参数。

你可能感兴趣的:(java并发编程)