Java面试内容精讲 - 并发编程volatile

第一章 volatile关键字概述

1.1 多线程下变量的不可见性

1.1.1 概述

在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改了共享变量的值后,另一个线程不能直接看到该线程修改后的变量的最新值。

通过volatile关键字,可以让该变量值可见。

package com.dhu.concurrency.volatil.concurrent;

/**
 * 目标:多线程下变量访问的不可见性现象
 * 		1.定义一个成员变量
 * 		2.开启两个线程,一个线程负责修改,另外一个负责读取。
 * @author zhou
 *
 */
public class VisibilityDemo01 {
	public static void main(String[] args) {
		//开启一个子线程
		MyThread myThread = new MyThread();
		myThread.start();
		
		//主线程
		while(true) {
			if(myThread.isFlag()) {
				System.out.println("主线程循环执行");
			}
		}
	}
	

}
class MyThread extends Thread {
	private boolean flag = false;
	
	@Override
	public void run() {
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//触发 修改共享成员变量
		flag = true;
		System.out.println(flag);
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	
}

Java面试内容精讲 - 并发编程volatile_第1张图片

可以看到,子线程中已经将flag设置为true,但main()线程中始终没有读到修改后的最新值

1.1.2 问题分析

Java面试内容精讲 - 并发编程volatile_第2张图片

1.1.3 小结

可见性问题的原因:

所有共享变量都存在于主内存中,每个线程有自己的本地内存,而且线程读取共享数据也是通过本地内存交换的,所以导致了可见性问题。

1.2 变量不可见性解决方案

第一种是加锁,第二种是使用volatile关键字。

1.2.1 解决方案1 - 加锁

Java面试内容精讲 - 并发编程volatile_第3张图片

Java面试内容精讲 - 并发编程volatile_第4张图片

加锁能够保证多线程下访问共享变量的可见性的原因:

Java面试内容精讲 - 并发编程volatile_第5张图片

1.2.2 volatile关键字修饰

使用volatile关键字修饰该变量:

//共享的成员变量
	private volatile boolean flag = false;

Java面试内容精讲 - 并发编程volatile_第6张图片

工作原理:

第5步,会通过底层的总线机制去通知主线程中的变量 flag = false 已经失效,因为共享变量 flag 加了 volatile,其它线程就相当于有了一个嗅探机制,会去关注主内存中的变量是否被其它线程修改。一旦有被修改,就会将原来的工作内存变量副本失效,那么必然需要去主内存获取最新的flag变量值。

Java面试内容精讲 - 并发编程volatile_第7张图片

Java面试内容精讲 - 并发编程volatile_第8张图片

总结:一个线程修改了volatile修饰的变量,当修改写回主内存时,另外的线程立即看到最新的值。

 

第二章 volatile的其它特性

2.1 volatile特性概述

volatile可以实现并发下共享变量的可见性,此外:

volatile的原子性问题:volatile不能保证原子性操作。

禁止指令重排序:volatile可以防止指令重排序操作。

2.2 volatile不保证原子性

原子性:并发修改下的一中安全机制

Java面试内容精讲 - 并发编程volatile_第9张图片

2.2.1 问题原理说明

Java面试内容精讲 - 并发编程volatile_第10张图片

Java面试内容精讲 - 并发编程volatile_第11张图片

Java面试内容精讲 - 并发编程volatile_第12张图片

2.2.2 volatile原子性测试

多线程环境下,volatile可以保证共享数据的可见性,但不能保证对数据操作的原子性。对线程,volatile修饰的变量也是线程不安全的。

//共享变量
    private volatile int count = 0;

Java面试内容精讲 - 并发编程volatile_第13张图片

小结:多线程下,要保证数据的安全性,还需要使用锁机制。

2.2.3 问题解决 - 使用锁机制

使用加锁机制保证volatile修饰变量的原子性操作。

每一个线程获得锁,执行完之后,通过volatile关键字可以获取count的最新值。 

Java面试内容精讲 - 并发编程volatile_第14张图片

2.3 禁止指令重排序

2.3.1 概述

什么是重排序?

为了提高性能,编译器和处理器(cpu)常常会对既定的代码执行顺序进行指令重排序。

Java面试内容精讲 - 并发编程volatile_第15张图片

2.3.2 重排序的好处

重排序可以提高处理的速度

Java面试内容精讲 - 并发编程volatile_第16张图片

2.3.3 重排序问题案例演示

重排序虽然可以提高执行的效率,但是在并发执行下,JVM虚拟机底层并不能保证重排序带来的安全性问题。

Java面试内容精讲 - 并发编程volatile_第17张图片

确实出现了:cpu进行了指令的重排序

Java面试内容精讲 - 并发编程volatile_第18张图片

Java面试内容精讲 - 并发编程volatile_第19张图片

2.3.4 volatile禁止重排序

volatile修饰变量后可以实现禁止指令重排序,从而修正重排序可能带来的并发安全问题

public volatile static int a = 0, b = 0; //volatile修饰
	public volatile static int i = 0, j = 0; //volatile修饰

 

3、volatile使用场景

3.1 纯赋值操作

不能保证原子性,赋值操作没有问题(变量是可见的)

 

4、volatile与synchronized的区别

4.1 区别

  • volatile只能修饰实例变量和类变量(volatile的目的是保证多线程下变量访问的可见性。当一个线程修改该变量,其它线程可以看到其最新值),而synchronized(同步机制,多线程下线程安全)可以修饰方法以及代码块。
  • volatile保证数据的可见性,但是不保证原子性(多线程读写操作,不保证线程安全),而synchronized是排他(互斥)机制
  • volatile用于禁止指令重排序:volatile修饰的变量,在执行过程中可以用于禁止指令重排序,可以解决单例双重检查对象初始化代码执行乱序问题。
  • volatile可以看作轻量版的sychronized,volatile不保证原子性;但是如果是多个线程对一个共享变量进行赋值,而没有其它操作,那么可以用volatile代替synchronized。因为赋值操作本身是原子性,而volatile有保证可见性,所以可以保证线程安全。

5、volatile总结

Java面试内容精讲 - 并发编程volatile_第20张图片

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(并发编程与多线程)