1-volatile关键字 内存可见性

假设内存中有个变量a=1;需要进行运算得到a的新值。 

在这个过程中:是通过CPU运算进行操作的。计算的过程 是从内存中将变量a 复制一份然后计算后,将返回的值重新写入内存中的变量a。(CPU是进行运算的、而内存是用来存储的。这种特性导致运算过程必须是拷贝一份数据给cpu运算,然后将结果重新返回给内存)

上面的过程相当于一个线程操作,当有多个线程进行运算时那么就会出现一些问题:

由于每个线程都是拷贝一份数据而不是直接操作内存中的数据,因此就有如下问题 

内存可见性(Memory Visibility):

         一个内存读取内存数据、计算后、重新写入内存,此时其它线程能够马上看到发生的状态变化。即内存可见性

可见性错误:是指当读操作与写操作在不同的线程中执行时,无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。

在多线程开发过程中经常会遇到内存可见性可见性错误

此时 可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的 volatile 变量

Volatile关键字的介绍:

Java 提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。
可以将 volatile 看做一个轻量级的锁,

但是又与锁有些不同:

  1. 对于多线程,不是一种互斥关系
  2. 不能保证变量状态的“原子性操作”

volatile的作用:解决多个线程访问数据内存不可见问题

代码举例:

下面有一个类实现了Runnable接口,run方法进行修改flag的值

class ThreadDemo implements Runnable {

	private boolean flag = false;//未加任何修饰

	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
		}

		flag = true;//修改flag的值
		//修改成功后输出flag的值
		System.out.println("flag=" + isFlag());//调用isFlag返回flag值,并打印出来

	}

	public boolean isFlag() {
		return flag;
	}


}

 执行下面的TestVolatile的main方法

public class TestVolatile {
	
	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		new Thread(td).start();// 启动线程会执行run方法,然后this.flag=true
		
		while(true){
			if(td.isFlag()){//如果前面的线程修改flag成功并且通过isFlag返回为true则打印分割线
				System.out.println("------------------");
				break;//结束循环
			}
		}
		
	}

}

得到的结果:

new Thread(td).start();这里会输出一个flag=true

然后在下面的while(true)中的if(td.isFlag())会始终得到的是false,导致死循环,分割线里面的代码不会执行。

为什么会是这样?不是前面一个线程已经修改了flag的值(flag=true)?然后isFlag()不是返回true?

然后就会执行    System.out.println("------------------") 然后break结束循环?

执行结果的gif图如下,只会输出flag=true不会有分割线----------------

你是否已经疑惑了?

解答原因:作图说明

如图第一个线程修改了flag值并且会修改主存中的flag值,但是在while循环中并不知道线程一修改了flag,因为它自身有缓存,缓存中的flag始终为false所以会一直死循环。

那么如何解决问题呢?

方式一、加个synchronized(td)锁住td类即可解决,即在while循环中如图

		while(true){
			synchronized (td){
				if(td.isFlag()){
					System.out.println("------------------");
					break;
				}
			}

		}

加synchronized (td)后的执行效果如下面的gif图片

方式二、给flag变量加个volatile修饰符

即在变量前加个volatile即可

那么问题来了?那个方式更好呢?两种方式有什么优劣?

在上面的代码中

方式一用synchronized将td对象锁住了、那么导致每次进入while循环都需要去主存中去判断一下td对象是否有锁,此时就会从主存中刷新缓存,加锁的情况 如果另外一个线程对td对象进行操作 那么就会判断是否有锁,有锁则会阻塞。效率极低

而volatile则相当于直接操作主存中的flag对象,保证多个线程内存中的数据是可见的。

volatile会导致,底层的重排序会失效。 效率相对较低(比上面都不加的时候),但是比加锁的方式效率高

那么是否任何情况都可以用volatile?答案是不

因为volatile不具备“互斥性”和“原子性”,而锁有互斥性和原子性

 

猜你喜欢

转载自blog.csdn.net/qq_41813208/article/details/103519089