理解 JVM:Java 内存模型(二)——volatile

概述

java 内存模型的核心是围绕着在并发过程当中如何处理原子性、可见性、有序性这3个特性来展开的,它们是多线程编程的核心。java

  • 原子性(Atomicity): 是指一个操做是不可中断的,即便是多个线程同时执行的状况下,一个操做一旦开始,就不会被其它线程干扰。对于基本类型的读写操做基本都具备原子性的(在32位操做系统中 long 和 double 类型数据的读写不是原子性的,由于它们有64位)。
  • 可见性(Visibility): 是指在多线程环境下,当一个线程修改了某一个共享变量的值,其它线程可以马上知道这个修改。
  • 有序性(Ordering): 是指程序的执行顺序是按照代码的前后顺序执行的;对于这句话若是在单线程中全部的操做都是有序的,可是在多线程环境下,一个线程的操做相对于另一个线程的操做是无序的。

<!-- more -->编程


先行发生原则(happens-before)

先行发生是 Java 内存模型中定义的两个操做之间的偏序关系,这些先行关系无需任何同步器的协助就已经存在,能够在编码中直接使用。Java 内存模型对这些关系做了以下规则:缓存

  • 程序次序规则(Program Order Rule): 在一个线程内,程序安装代码顺序执行。即所谓的“线程内表现为串行的语义(Within-Thread As-If-Serial Semantics)”。
  • 管程锁定规则(Monitor Lock Rule): 一个 unlock 操做先行发生于后面对同一个锁的 lock 操做。
  • volatile 变量规则(Volatile Variable Rule): 对于一个 volatile 变量的写操做先行发生于此线程的每个动做。
  • 线程启动规则(Thread Start Rule): Thread 对象的 start() 方法先行发生于此线程的每个动做。
  • 线程终止规则(Thread Termination Rule): 线程中的全部操做先行发生于对此线程的终止检测。
  • 线程中断规则(Thread Interruption Rule): 对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
  • 对象终结规则(Finalizer Rule): 一个对象的初始化完成先行发生于它的 finalizer() 方法。
  • 传递性(Transitivity): 若是操做 A 先行发生于操做 B,操做 B 先行发生于 操做 C,能够推断出 操做 A 先行发生于操做 C。

关键字 volatile

volatile 修饰的变量保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的。由于当对普通变量进行读写的时候,每一个线程先从内存拷贝变量到CPU缓存中。若是计算机有多个CPU,每一个线程可能在不一样的CPU上被处理,这意味着每一个线程能够拷贝到不一样的CPU cache中。而volatile修饰的变量,JVM保证了每次读变量都从内存中读,跳过CPU cache这一步。volatile修饰的变量禁止进行指令重排序,因此能在必定程度上保证有序性。只能保证该变量所在的语句仍是原来的位置,并不能保证该语句以前或以后的语句是否被打乱。多线程

volatile 的特性

  1. 当一个变量被 volatile 修饰以后,能保证此变量对全部线程的可见性,即当一个线程修改了这个变量的值,新值对其它线程是当即可见的。
  2. 被 volatile 修饰的变量经过查询内存屏障来禁止指令重排序,因此能在必定程度上保证有序性。
  3. 对任意单个 volatile 变量的读/写具备原子性,但相似于 volatile++ 这种复合操做不具备原子性。 例如:
package com.pdh.test;

/**
 * volatile 复合操做测试
 *
 * @author pengdh
 * @date 2017/11/12
 */
public class VolatileDemo {
	//	申明 volatile 变量
	private static volatile int i = 0;
	//	计数
	private static final int COUNT = 10;

	/**
	 * 对 volatile 变量复合运算
	 */
	private static void increase() {
		i++;
	}

	public static void main(String[] args) {
		// 启动 10 个线程分别对 i 进行 10000 次计算,正常状况结果为 100000 
		for (int j = 0; j < COUNT; j++) {
			new Thread(() -> {
				for (int k = 0; k < 10000; k++) {
					increase();
				}
			}).start();
		}
		// 等待全部累加线程所有执行结束,这里不一样 ide 中线程存活数不同,
		// 该示例代码在 idea 中运行,会多出一个 Monitor Ctrl-Break 线程,故条件是 > 2,
		// 若是在 Eclipse 中条件应为 > 1
		while (Thread.activeCount() > 2) {
			Thread.yield();
		}
		System.out.println(i);
	}
}

如上代码正常运行结果应该打印100000,但实际结果基本得不到正确结果。这说明了 volatile 变量的复合运算并不具备原子性,想要获得正确结果,须要对 volatile 变量运算操做加锁或者加上同步块。并发

package com.pdh.test;

/**
 * volatile 复合操做测试
 *
 * @author pengdh
 * @date 2017/11/12
 */
public class VolatileDemo {
	//	申明 volatile 变量
	private static volatile int i = 0;
	//	计数
	private static final int COUNT = 10;

	/**
	 * 对 volatile 变量复合运算,使用 synchronized 同步
	 */
	private static synchronized void increase() {
		i++;
	}

	public static void main(String[] args) {
		// 启动 10 个线程分别对 i 进行 10000 次计算,正常状况结果为 100000
		for (int j = 0; j < COUNT; j++) {
			new Thread(() -> {
				for (int k = 0; k < 10000; k++) {
					increase();
				}
			}).start();
		}
		// 等待全部累加线程所有执行结束,这里不一样 ide 中线程存活数不同,
		// 该示例代码在 idea 中运行,会多出一个 Monitor Ctrl-Break 线程,故条件是 > 2,
		// 若是在 Eclipse 中条件应为 > 1
		while (Thread.activeCount() > 2) {
			Thread.yield();
		}
		System.out.println(i);
	}
}

volatile 适合场景

volatile适用于不须要保证原子性,但却须要保证可见性的场景。一种典型的使用场景是用它修饰用于中止线程的状态标记,如:app

package com.pdh.test;


/**
 * volatile 复合操做测试
 *
 * @author pengdh
 * @date 2017/11/12
 */
public class VolatileDemo {
	//	申明 volatile 变量
	private volatile boolean flag = false;
	//	计数
	private static final int COUNT = 10;

	/**
	 * 使用 volatile 变量做为线程结束标志
	 */
	private void start() {
		new Thread(() -> {
			while (!flag) {
				System.out.println("Thread is running");
			}
		}).start();
	}

	private void shutdown() {
		flag = true;
		System.out.println("Thread is stop");
	}

	public static void main(String[] args) throws InterruptedException {
		VolatileDemo demo = new VolatileDemo();
		demo.start();
		Thread.sleep(2000);
		demo.shutdown();
	}
}

使用 volatile 的意义

在只需保证可见性的状况下,volatile 的同步机制性能要优于锁。ide


参考文献

  • <span style="color:red;font-size:14px;font-family:Microsoft YaHei;">深刻理解 Java 虚拟机</span>

欢迎扫一扫关注 程序猿pdh 公众号!性能

欢迎关注公众号 程序猿pdh

相关文章
相关标签/搜索