Java Threads - The volatile keyword
在网上看到很多关于 volatile 关键字的说明和使用问题, 今天引用Java Threads中的解释,感觉全面而详细,可惜是英文的.
这里很清晰的揭示了volatile 本身并不处理java读取数据的原子性问题,而是强制线程对数据的读写必须及时反映到主内存.
以下说明了volatile时, 其不能用于 long 和double 类型数据的根本和直接原因,其他很多解释都犯了概念性的错误.
其他原子类型数据的loading 和storing的原子性是由Java语言自身定义的,并不是由volatile来修饰实现的,这里很多网上的相关解释存在概念上的错误。
The Volatile Keyword
There is still one more threading issue in this example, and it has to do with the setDone() method. This method is called from the event-dispatching thread when the Stop button is pressed; it is called by an event handler (an actionPerformed() method) that is defined as an inner class to the SwingTypeTester class. The issue here is that this method is executed by the event-dispatching thread and changes data that is being used by another thread: the done flag, which is accessed by the thread of the AnimatedDisplayCanvas class.
So, can't we just synchronize the two methods, just as we did previously? Yes and no. Yes, Java's synchronized keyword allows this problem to be fixed. But no, the techniques that we have learned so far will not work. The reason has to do with the run() method. If we synchronized both the run() and setDone() methods, how would the setDone() method ever execute? The run( ) method does not exit until the done flag is set, but the done flag can't be set because the setDone() method can't execute until the run() method completes.
Definition: Scope of a LockThe scope of a lock is defined as the period of time between when the lock is grabbed and released. In our examples so far, we have used only synchronized methods; this means that the scope of these locks is the period of time it takes to execute the methods. This is referred to as method scope. Later in this chapter, we'll examine locks that apply to any block of code inside a method or that can be explicitly grabbed and released; these locks have a different scope. We'll examine this concept of scope as locks of various types are introduced. |
The problem at this point relates to the scope of the lock: the scope of the run() method is too large. By synchronizing the run() method, the lock is grabbed and never released. There is a way to shrink the scope of a lock by synchronizing only the portion of the run() method that protects the done flag (which we examine later in this chapter). However, there is a more elegant solution in this case.
The setDone() method performs only one operation with the done flag: it stores a value into the flag. The run() method also performs one operation with the done flag: it reads the value during each iteration of the loop. Furthermore, it does not matter if the value changes during the iteration of these methods, as each loop must complete anyway.
The issue here is that we potentially have a race condition because one piece of data is being shared between two different threads. In our first example, the race condition came about because the threads were accessing multiple pieces of data and there was no way to update all of them atomically without using the synchronized keyword. When only a single piece of data is involved, there is a different solution.
Java specifies that basic loading and storing of variables (except for long and double variables) is atomic. That means the value of the variable can't be found in an interim state during the store, nor can it be changed in the middle of loading the variable to a register. The setDone() method has only one store operation; therefore, it is atomic. The run( ) method has only one read operation. Since the rest of the run() method does not depend on the value of the variable remaining constant, the race condition should not exist in this case.
Unfortunately, Java's memory model is a bit more complex. Threads are allowed to hold the values of variables in local memory (e.g., in a machine register). In that case, when one thread changes the value of the variable, another thread may not see the changed variable. This is particularly true in loops that are controlled by a variable (like the done flag that we are using to terminate the thread): the looping thread may have already loaded the value of the variable into a register and does not necessarily notice when another thread changes the variable.
One way to solve this problem is to provide setter and getter methods for the variable. We can then simply synchronize access by using the synchronized keyword on these methods. This works because acquiring a synchronization lock means that all temporary values stored in registers are flushed to main memory. However, Java provides a more elegant solution: the volatile keyword. If a variable is marked as volatile, every time the variable is used it must be read from main memory. Similarly, every time the variable is written, the value must be stored in main memory. Since these operations are atomic, we can avoid the race condition in our example by marking our done flag as volatile.
In most releases of the virtual machine prior to JDK 1.2, the actual implementation of Java's memory model made using volatile variables a moot point: variables were always read from main memory. In subsequent iterations of Java, up to and including J2SE 5.0, implementations of virtual machines became more sophisticated and introduced new memory models and optimizations: this trend is expected to continue in future versions of Java. With all modern virtual machine implementations, developers can not assume that variables will be accessed directly from main memory.
So why is volatile necessary? Or even useful? Volatile variables solve only the problem introduced by Java's memory model. They can be used only when the operations that use the variable are atomic, meaning the methods that access the variable must use only a single load or store. If the method has other code, that code may not depend on the variable changing its value during its operation. For example, operations like increment and decrement (e.g., ++ and --) can't be used on a volatile variable because these operations are syntactic sugar for a load, change, and a store.
As we mentioned, we could have solved this problem by using synchronized setter and getter methods to access the variable. However, that would be fairly complex. We must invoke another method, including setting up parameters and the return variable. We must grab and release the lock necessary to invoke the method. And all for a single line of code, with one atomic operation, that is called many times within a loop. The concept of using a done flag is common enough that we can make a very strong case for the volatile keyword.
The requirements of using volatile variables seem overly restrictive. Are they really important? This question can lead to an unending debate. For now, it is better to think of the volatile keyword as a way to force the virtual machine not to make temporary copies of a variable. While we can agree that you might not use these types of variables in many cases, they are an option during program design. In Chapter 5, we examine similar variables (atomic variables) that are less restrictive: variables that are not only atomic but can be built on using programming techniques. This allows us to build complex atomic functionality.
How does volatile work with arrays? Declaring an array volatile makes the array reference itself volatile. The elements within the array are not volatile; the virtual machine may still store copies of individual elements in local registers. There is no way to specify that the elements of an array should be treated as volatile. Consequently, if multiple threads are going to access array elements, they must use synchronization in order to protect the data. Atomic variables can also help in this situation.
Java Threads - The volatile keyword的更多相关文章
- Java内存可见性volatile
概述 JMM规范指出,每一个线程都有自己的工作内存(working memory),当变量的值发生变化时,先更新自己的工作内存,然后再拷贝到主存(main memory),这样其他线程就能读取到更新后 ...
- java中的volatile关键字
java中的volatile关键字 一个变量被声明为volatile类型,表示这个变量可能随时被其他线程改变,所以不能把它cache到线程内存(如寄存器)中. 一般情况下volatile不能代替syn ...
- Java 并发 关键字volatile
Java 并发 关键字volatile @author ixenos volatile只是保证了共享变量的可见性,不保证同步操作的原子性 同步块 和 volatile 关键字机制 synchroniz ...
- Java并发编程 Volatile关键字解析
volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了 ...
- java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)
概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...
- Java并发之volatile二
使用volatilekeyword的场景 Volatile 变量具有 synchronized 的可见性特性.可是不具备原子特性.这就是说线程可以自己主动发现 volatile 变量的最新值.Vola ...
- Java内存模型-volatile的内存语义
一 引言 听说在Java 5之前volatile关键字备受争议,所以本文也不讨论1.5版本之前的volatile.本文主要针对1.5后即JSR-133针对volatile做了强化后的了解. 二 vol ...
- Java 并发:volatile 关键字解析
摘要: 在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保 ...
- Java中的volatile关键字的功能
Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...
随机推荐
- ABAP ALV F4帮助
ALV F4帮助, 选值保存到ALV. TYPE-POOLS:slis. CLASS lcl_event_receiver DEFINITION DEFERRED. DATA: gt_fcat TYP ...
- RK3288 make otapackage 出错的问题【转】
本文转载自:http://blog.csdn.net/u010439962/article/details/51734631 Installed file list: out/target/produ ...
- 【转】Vuex 学习总结
对于很多新手来说,只是阅读文档是不好消化,我的建议是看看 vuex 的实例,通过研究实例来学习vuex.这样就会好理解多了.如果还是不能理解,最好办法就是先把store 的四个属性:state, ge ...
- python-----用多张图片生成视频
代码如下 #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019/2/19 16:42 # @Author : xiaodai # - ...
- gcc编译系统
一. C语言编译过程 C语言的编译过程可分为四个阶段: 1.预处理(Preprocessing) 对源程序中的伪指令(即以#开头的指令)和特殊符号进行处理的过程. 伪指令包括:1)宏定义指令: 2)条 ...
- Java 学习路线建议
在大家看之前,我要先声明两点.1.由于我本人是Java后端开发出身,因此所推荐的学习内容是Java Web和Java后端开发的路线,非Java Web和Java后端开发的同学请适当参考其学习思想即可, ...
- python创建文件
创建文件: 1. os.mknod(“test.txt”) 创建空文件 2. open(“test.txt”,w) 直接打开一个文件,如果文件不存在则创建文件 import os def mkdir_ ...
- IJ:目录
ylbtech-IJ:目录 1.返回顶部 2.返回顶部 3.返回顶部 4.返回顶部 5.返回顶部 6.返回顶部 7.返回顶部 8.返回顶部 9.返回顶部 10. ...
- Javaweb的9大内置对象
request(请求) response(响应) session(一个用户存放数据,安全) application(一个项目一般有一个,多用户共享存简单数据) out(输出,在页面输出内容) conf ...
- Unity优化总览
CPU GC 序列化与反序列化,如protobuff,json解析 String的频繁构造,拼接,如ToString()会生成字符串,Object.name会返回拷贝 闭包和匿名函数,在闭包中调用外部 ...