Java高并发之volatile 关键字

1、volatile简介

(1)volatile关键字只能修饰变量。
(2)volatile关键字是线程同步的轻量级实现,被volatile修饰的变量,具有可见性,即每次线程使用这个变量时,都是读取主内存中这个变量的最新值。
(3)同时被volatile修饰的变量还具有有序性,为了性能优化,jvm在不改变正确语义的情况下,会允许编译期和处理器对指令序列进行重新排序,而volatile可以阻止jvm重新排序。

Test test = new Test();

这行代码是分为三部执行的

1.为test分配内存空间;
2.初始化test;
3.将test指向分配的内存地址;

由于JVM有指令重排的特性,所有上述步骤可能为1->3->2,在逻辑上是可行的。如果线程A执行了1和3,线程B如果这时判断test是否有值,会得到有值的结果,但是使用test却会报错,因为test没有初始化。所有volatile可以组织指令重排,使之按照1->2->3的顺序执行。
(4)volatile可以保证变量的可见性,但是不能保证原子性。即多线程的情况不能保证线程安全,可能会出现脏读的情况。

2、Java内存模型

Java高并发之volatile 关键字_第1张图片
在当前的Java内存模型中,线程可以把变量保存为本地内存(如机器的寄存器)中,线程操作该变量时就是从本地内存中取值,而不是从主内存中取值,如果另一个线程在主内存中修改了这个变量的值,当前线程是感知不到的,会造成数据的不一致问题,也就是脏读
如果该变量使用了volatile修饰的话,那么线程每次操作该变量都会直接从主内存中读取数据、写入数据。让数据在线程间有可见性
Java高并发之volatile 关键字_第2张图片

3、为什么volatile能保证可见性,但是不能保证原子性

package com.kk.first.thread;

public class TestVolatile extends Thread {
     
	private static volatile Integer a = 0;
	@Override
	public void run() {
     
		int i = 0;
		while (i < 1000) {
     
			add();
			i++;
		}		
	}
	private static void add() {
     
		a++;
	}
	public static void main(String[] args) {
     
		TestVolatile tv = new TestVolatile();
		for (int i = 0; i < 10; i++) {
     
			Thread t = new Thread(tv);
			t.start();
		}
		try {
     
			Thread.sleep(10000);
		} catch (InterruptedException e) {
     
			e.printStackTrace();
		}
		System.out.println(a); // 7595
	}
}

如果volatile能保证原子性,那么执行结果a应为10000,但是结果却小于10000。所以volatile修饰的变量并不能保证原子性。虽然其修饰的变量是直接从主内存中读取数据,但是多个线程竞争的话,并且对数据有计算操作的话,如volatile修饰变量a在主内存中值为0,A线程读取的时候值为0,在A线程执行操作为加1,这时B线程也读取a的值,此时A线程执行的结果还没有写入到主内存中,所以B线程读取到的值为a=0,B线程也做+1操作。两个线程都执行完,将值写入主内存中。最终得到a的值为1,也就是两个线程都对a做的+1的操作,但是最后的结果却是a=1。

4、总结

所以volatile经常和synchronized关键字不是互斥的,是互补的,它们经常配合使用,比如通过双重检验锁实现线程安全的单例模式
详细可见这篇博文Volatile和Synchronized关键字实现单例模式(线程安全)
synchronized相关详解Java高并发之synchronized关键字

你可能感兴趣的:(java基础,java)