Java多线程学习(三)volatile关键字

转载请备注地址:https://juejin.im/post/5ab70c996fb9a028c812d06fhtml

系列文章传送门:java

Java多线程学习(一)Java多线程入门程序员

Java多线程学习(二)synchronized关键字(1)面试

Java多线程学习(二)synchronized关键字(2)编程

Java多线程学习(三)volatile关键字微信

Java多线程学习(四)等待/通知(wait/notify)机制多线程

系列文章将被优先更新于微信公众号“Java面试通关手册”,欢迎广大Java程序员和爱好技术的人员关注。并发

本节思惟导图:编程语言

volatile关键字

思惟导图源文件+思惟导图软件关注微信公众号:**“Java面试通关手册”回复关键字:“Java多线程”**免费领取。ide

一 简介

先来看看维基百科对“volatile关键字”的定义:

在程序设计中,尤为是在C语言、C++、C#和Java语言中,使用volatile关键字声明的变量或对象一般具备与优化、多线程相关的特殊属性。一般,volatile关键字用来阻止(伪)编译器认为的没法“被代码自己”改变的代码(变量/对象)进行优化。如在C语言中,volatile关键字能够用来提醒编译器它后面所定义的变量随时有可能改变,所以编译后的程序每次须要存储或读取这个变量的时候,都会直接从变量地址中读取数。若是没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,若是这个变量由别的程序更新了的话,将出现不一致的现象。据

在C环境中,volatile关键字的真实定义和适用范围常常被误解。虽然C++、C#和Java都保留了C中的volatile关键字,但在这些编程语言中volatile的用法和语义却截然不同。

Java中的“volatile关键字”关键字:

在 JDK1.2 以前,Java的内存模型实现老是从主存(即共享内存)读取变量,是不须要进行特别的注意的。而在当前的 Java 内存模型下,线程能够把变量保存本地内存(好比机器的寄存器)中,而不是直接在主存中进行读写。这就可能形成一个线程在主存中修改了一个变量的值,而另一个线程还继续使用它在寄存器中的变量值的拷贝,形成数据的不一致

数据的不一致
要解决这个问题,就须要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。
volatile关键字的可见性

二 volatile关键字的可见性

volatile 修饰的成员变量在每次被线程访问时,都强迫从主存(共享内存)中重读该成员变量的值。并且,当成员变量发生变化时,强迫线程将变化值回写到主存(共享内存)。这样在任什么时候刻,两个不一样的线程老是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性

RunThread.java

private boolean isRunning = true;
 int m;
	public boolean isRunning() {
		return isRunning;
	}
	public void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}
	@Override
	public void run() {
		System.out.println("进入run了");
		while (isRunning == true) {
			int a=2;
			int b=3;
			int c=a+b;
			m=c;
		}
		System.out.println(m);
		System.out.println("线程被中止了!");
	}
}
复制代码

Run.java

public class Run {
	public static void main(String[] args) throws InterruptedException {
		RunThread thread = new RunThread();
		
		thread.start();
		Thread.sleep(1000);
		thread.setRunning(false);

		System.out.println("已经赋值为false");
	}
}
复制代码

运行结果:

死循环

RunThread类中的isRunning变量没有加上volatile关键字时,运行以上代码会出现死循环,这是由于isRunning变量虽然被修改可是没有被写到主存中,这也就致使该线程在本地内存中的值一直为true,这样就致使了死循环的产生。

解决办法也很简单:isRunning变量前加上volatile关键字便可。

isRunning变量前加上volatile关键字
这样运行就不会出现死循环了。 加上volatile关键字后的运行结果:
加上volatile关键字后的运行结果

你是否是觉得到这就完了?

不存在的!!!(这里还有一点须要强调,下面的内容必定要看,否则你在用volatile关键字时会很迷糊,由于书籍几乎都没有提这个问题)

假如你把while循环代码里加上任意一个输出语句或者sleep方法你会发现死循环也会中止,无论isRunning变量是否被加上了上volatile关键字。

加上输出语句:

while (isRunning == true) {
			int a=2;
			int b=3;
			int c=a+b;
			m=c;
			System.out.println(m);
		}
复制代码

加上sleep方法:

while (isRunning == true) {
			int a=2;
			int b=3;
			int c=a+b;
			m=c;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
复制代码

这是为何呢?

由于:JVM会尽力保证内存的可见性,即使这个变量没有加同步关键字。换句话说,只要CPU有时间,JVM会尽力去保证变量值的更新。这种与volatile关键字的不一样在于,volatile关键字会强制的保证线程的可见性。而不加这个关键字,JVM也会尽力去保证可见性,可是若是CPU一直有其余的事情在处理,它也没办法。最开始的代码,一直处于死循环中,CPU处于一直占用的状态,这个时候CPU没有时间,JVM也不能强制要求CPU分点时间去取最新的变量值。而加了输出或者sleep语句以后,CPU就有可能有时间去保证内存的可见性,因而while循环能够被终止

三 volatile关键字能保证原子性吗?

《Java并发编程艺术》这本书上说保证可是在自增操做(非原子操做)上不保证,《Java多线程编程核心艺术》这本书说不保证。

我我的更倾向于这种说法:volatile没法保证对变量原子性的。我我的感受《Java并发编程艺术》这本书上说volatile关键字保证原子性吗可是在自增操做(非原子操做)上不保证这种说法是有问题的。只是我的见解,但愿不要被喷。能够看下面测试代码:

MyThread.java

public class MyThread extends Thread {
	volatile public static int count;

	private static void addCount() {
		for (int i = 0; i < 100; i++) {
			count=i;
		}
		System.out.println("count=" + count);

	}
	@Override
	public void run() {
		addCount();
	}
}
复制代码

Run.java

public class Run {
	public static void main(String[] args) {
		MyThread[] mythreadArray = new MyThread[100];
		for (int i = 0; i < 100; i++) {
			mythreadArray[i] = new MyThread();
		}

		for (int i = 0; i < 100; i++) {
			mythreadArray[i].start();
		}
	}

}
复制代码

运行结果:

上面的“count=i;”是一个原子操做,可是运行结果大部分都是正确结果99,可是也有部分不是99的结果。

运行结果
解决办法

使用synchronized关键字加锁。(这只是一种方法,Lock和AtomicInteger原子类均可以,由于以前学过synchronized关键字,因此咱们使用synchronized关键字的方法)

修改MyThread.java以下:

public class MyThread extends Thread {
	public static int count;

	synchronized private static void addCount() {
		for (int i = 0; i < 100; i++) {
			count=i;
		}
		System.out.println("count=" + count);
	}
	@Override
	public void run() {
		addCount();
	}
}
复制代码

这样运行输出的count就都为99了,因此要保证数据的原子性仍是要使用synchronized关键字

四 synchronized关键字和volatile关键字比较

  • volatile关键字是线程同步的轻量级实现,因此volatile性能确定比synchronized关键字要好。可是volatile关键字只能用于变量而synchronized关键字能够修饰方法以及代码块。synchronized关键字在JavaSE1.6以后进行了主要包括为了减小得到锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各类优化以后执行效率有了显著提高,实际开发中使用synchronized关键字仍是更多一些
  • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
  • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字二者都能保证。
  • volatile关键字用于解决变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。

参考:

《Java多线程编程核心技术》

《Java并发编程的艺术》

极客学院Java并发编程wiki: http://wiki.jikexueyuan.com/project/java-concurrency/volatile1.html

相关文章
相关标签/搜索