miracle just wanna be better

volatile关键字

2019-11-05
miracle

为什么用volatile

多线程开发有三个特性

  1. 原子性
  2. 可见性
  3. 有序性

原子性(Atomicity)

一个操作中cpu不可以中途暂停然后再调度,要不就执行完成,要么不执行.java提供了很多机制来保证原子性.
如a++就不满足原子性,可能运行的时候做了一半就不做了。 为了解决这个问题,java提供了很多类,比如AtomicInteger,AtomicLong,AtomicReference等

可见性(visibility)

当一个线程修改了线程共享变量的值,其它线程能够立刻得知这个修改.
每个线程都有一份自己的本地内存,所有线程公用一份主内存,如果一个线程对主内存的数据进行了修改,而此时另一个线程不知道是否已经发生了修改,就说此时是不可见的.
这种不可见的状况会带来一个问题,两个线程有可能操作同一份但是值不一样的数据,于是volatile关键字就起作用了.

volatile关键字的作用:一个线程在对主内存的某一份数据进行更改时,改完之后会立刻刷新到主内存.并且会强制让缓存了该变量的线程中数据清空,必须从主内存重新读取最新数据,这样就保证了可见性.

有序性

程序执行的顺序按照代码的先后顺序执行就叫做有序性,但是有时候程序的执行并不会遵循.
比如:

int i=1;
int j=2;

这两行代码,程序可能会发生指令重排序,就是代码会重新排列.
为了防止重排序,java提供了很多机制,比如volatile关键字.

volatile不能保证原子性

volatile修饰的变量a做++操作时,实际的指令包括三个(得到a的值,a+1,将a+1赋值给a),因此可能会出现多个线程交叉执行的结果。

public class Test {
	private static volatile int a=0;
	public static void main(String[] args) {
		Thread[] threads=new Thread[5];
		for(int i=0;i<5;i++){
			threads[i]=new Thread(()->{
				for(int j=0;j<10;j++){
					try {
						System.out.println(++a);
						Thread.sleep(1000);
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			});
			threads[i].start();
		}
	}
}

结果13245678109111213141516171819182020…
出现了相邻或不相邻的两个数字是重复的.这说明volatile不能保证线程的原子性.

使用synchronized修饰,或者Lock锁,或者AtomicInteger

单例模式的双重锁为什么使用volatile

public class Test2 {
	public static volatile Test2 test2;
	public static Test2 getInstance(){
		if(test2==null){
			synchronized(Test2.class){
				if(test2==null){
					test2=new Test2();
				}
			}
		}
		return test2;
	}
}

为什么这里要加volatile关键字呢?把test=new Test2()拆分,可以分为三步

  1. memory=allocate();//分配内存
  2. ctorInstanc(memory);//初始化对象
  3. test2=memory//设置s指向刚分配的地址
    如果没有volatile,可能会发生指令重排序,在编译运行时,从1-2-3排序为1-3-2.此时两个线程同时进来的时候发生了可见性问题,就是一个线程执行了1-3,另一个线程一进来直接返回还未执行2得null对象.而我们volatile关键字已经说过了,可以防止指令重排序.

下一篇 set集合

Comments

Content