volatile学习笔记

Posted 等待戈多儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了volatile学习笔记相关的知识,希望对你有一定的参考价值。

参考《深入理解Java虚拟机-JVM高级特性与最佳实践》12.3.3对于volatile型变量的特殊规则

volatile是用来修饰变量,是jvm最轻量级的同步机制。在大多数场景下,volatile的总开销要比锁低。volatile的读操作性能与普通变量几乎一样,写操作会稍微慢一些:它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序。

volatile的特性:

1.保证此变量对所有线程具有可见性。当一个线程修改了这个变量的值,新值对于其他线程来说是立即得知的。普通变量在线程间的传递需要通过主内存来完成,例如线程A修改一个普通变量的值,然后向主内存进行写回,另外一个线程B在线程A回写完成之后再从主内存进行读取操作,新变量值才对线程B可见。

虽然volatile变量对所有线程是立即可见的,是一致的,但是基于volatile的运算在并发下不是安全的,因为Java里面的运算不是原子操作。

synchronized和final也能实现这种可见性。

final(终态的不可变的)可见性:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把this引用传递出去,那么在其他线程中能看见final字段的值。无需同步就能被其他线程正确访问。(static表示跟随类的,JVM在类加载时static修饰的变量时分配一次内存)

所以:运算结果并不依赖变量的当前值,或者能够确保只有单一线程修改变量值;变量不需要与其他的状态变量共同参与不变约束时使用volatile,否则使用其他锁。

2.禁止指令重排序优化。指令重排序优化是机器级的优化操作,赋值操作的顺序可能不会与程序代码中的执行顺序一致。volatile实现禁止重排序优化的方法是在本地代码中插入许多内存屏障指令来保证处理器不发生乱序,重排序时不能把后面的指令重排序内存屏障之前的位置。

Java内存模型中对volatile变量定义的特殊规则:

1.在工作内存中,每次在使用volatile变量前都必须先从主内存刷新最新的值,用于保证能看见其他线程对该变量所做修改后的值。

2.在工作内存中,每次修改volatile变量后都必须立刻同步回主内存中,用于保证其他线程能看到自己对该变量所做的修改。

3.volatile变量不会被指令重排序优化,保证代码执行顺序与程序的顺序相同。

volatile在并发的时候不一定是线程安全的:

 public class VolatileTest 
	public static volatile int race = 0;
	public static void increase()
		race++;
	
	private static final int THREAD_COUNT = 20;
	public static void main(String[] args) 
		Thread[] threads = new Thread[THREAD_COUNT];
		for(int i=0;i<THREAD_COUNT;i++)
			threads[i] = new Thread(new Runnable() 	
				
				@Override
				public void run() 				 
					for(int i=0;i<10000;i++)
						increase();
					
					
				
			);
			threads[i].start();
		
		//Thread.activeCount()返回当前线程的线程组中活动线程的数目
		//即表示除了主线程还有别的线程在执行,所以让主线程让出CPU
		while(Thread.activeCount()>1)
			//Thread.yield() 暂停当前正在执行的线程对象,并执行其他线程。
			//在多线程程序中,为了防止某线程独占CPU资源,可以让当前执行的线程"休息"一下。
			//并不保证下一个运行的线程就一定不是该线程。
			Thread.yield();
		
		System.out.println(new Date()+":"+race);
		//Sun May 08 15:14:45 GMT+08:00 2016:185023
	


以上示例可以得出这个结论输出的结果得不到预期的200000,但是将increase()方法改为synchronized (public static synchronized void increase())修饰的就能得到想要的结果。

如果运算结果不依赖变量的当前值(volatile一种使用场景):

 volatile boolean currentval;
	public void shutdown()
		currentval = true;
	
	
	public void work()
		while(!currentval)
			dosth();
		
	

禁止指令重排序

volatile boolean initialized = false;
	void threadA()
		//线程A中执行一些事情
		dosth1();
		dosth2();
		//volatile的变量initialized的赋值会在dosth1()和dosth2()之前执行
		//其禁止了指令重排序
		initialized = true;
	
	
	void threadB()
		//线程B中执行
		while(!initialized)
			sleep();//等待
		
		dosthAboutA();//用到线程A中执行的事情
	

DCL(双锁检测锁定)单例模式:

关于单例模式的研究推荐:http://crud0906.iteye.com/blog/576321

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式就是为了避免不一致状态,避免政出多头。

所谓线程安全是指:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

原子操作的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形


public class Singleton 
	  //volatile保证其对其他线程立即可见,避免指令重排
	  private volatile static Singleton singleton;
	  private Singleton();//私有化无参构造方法
	  public static Singleton getInstance()
		//双重检测加锁
		if(singleton == null)
			synchronized (Singleton.class)//第一次创建实例时才同步
				if(singleton == null)
					singleton = new Singleton();
				
			
		
		return singleton;
	  

 
 



以上是关于volatile学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

C++ 学习笔记:C++ 中 Volatile 变量学习

volatile 学习笔记

Java:java学习笔记之volatile关键字的简单理解和使用

Java高并发学习笔记:volatile关键字

C语言学习笔记--const 和 volatile关键字

JAVA多线程学习- 三:volatile关键字