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,基本上会导致这样的结果:要么无法编写多 ...
随机推荐
- Django创建数据表
Django中创建表. 用的django项目自带的sqlite数据库,创建完毕后将表注冊到jdango.admin,就能够在浏览器在管理了. 在django项目的models.py文件里: from ...
- HTML的高富帅
1,前端的内容(组成部分有以下三部分) HTML CSS JS 裸体的人 穿上好看的衣服 ...
- caioj1421&&hdu2167: [视频]【状态压缩】选数
%hz大佬..这道题的状态压缩简直匪夷所思(其实是我孤陋寡闻,而且我以前的博客竟然写了这题..水啊) 嗯这题可以发现,我们可以用一个二进制表示一行的状态,1表示选0反之,可以发现行与行之间可选的范围是 ...
- java实现io读取数据
ServletInputStream inputStream = request.getInputStream(); BufferedReader br = new BufferedReader(ne ...
- Synchronized的使用和注意事项
synchronized: 1.取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁: 2.多个线程访问的必须是同一个对象. 3.当一个线程执行的代码出现异常时,其所持有的锁会自动释放 4.A线程 ...
- P3398 仓鼠找sugar 又一次血的教训
做什么题都要注意数组的大小,不要犯下数组越界的错误(温馨(狠心)提示): 做了好多遍就是不对,原来是[20]的数组,在for下循环1——>20,神奇爆零: 链接:https://www.luog ...
- UI:归档、反归档、数据持久化
支持的文件读写类型:字符串.数组.字典.NSdata (可变的.不可变的.共有8个类) 对于数组.字典在写入文件时,其中的元素也必须是以上四种类型之一. 支持的数据类型有限.且简单 写入文件: 字符 ...
- Java Socket线程的设计原理介绍
转自:http://developer.51cto.com/art/201003/190001.htm Java Socket线程我们经常会用到的技术,但是有很多程序员还是有不少的使用问题,下面我们就 ...
- Markdown——让你专注写作
Markdown--让你专注写作 前些日子,写作的时候总会因为排版而耽误时间,甚至因为排版而把写作的专注力转移到了貌似相关的排版上.诚然,一个好的排版,会让读者有良好的体验,可是对于写作的人来说,这却 ...
- Gym 100548F Color (数论容斥原理+组合数)
题意:给定 m 种颜色,把 n 盆花排成一直线的花涂色.要求相邻花的颜色不相同,且使用的颜色恰好是k种.问一共有几种涂色方法. 析:首先是先从 m 种颜色中选出 k 种颜色,然后下面用的容斥原理,当时 ...