tmg*_*mgr 71 java concurrency multithreading volatile
我正在阅读Java中的volatile关键字并完全理解它的理论部分.
但是,我正在寻找的是一个很好的案例,它展示了如果变量不是易变的话会发生什么.
下面的代码片段无法正常工作(来自aioobe)
class Test extends Thread {
boolean keepRunning = true;
public void run() {
while (keepRunning) {
}
System.out.println("Thread terminated.");
}
public static void main(String[] args) throws InterruptedException {
Test t = new Test();
t.start();
Thread.sleep(1000);
t.keepRunning = false;
System.out.println("keepRunning set to false.");
}
}
Run Code Online (Sandbox Code Playgroud)
理想情况下,如果keepRunning不是volatile,则线程应该继续无限运行.但是,它会在几秒钟后停止.
我有两个基本问题: -
Nar*_*hai 48
易失性 - >保证可见性而非原子性
同步(锁定) - >保证可见性和原子性(如果正确完成)
易失性不能代替同步
仅在更新引用且不对其执行某些其他操作时使用volatile.
例:
volatile int i = 0;
public void incrementI(){
i++;
}
Run Code Online (Sandbox Code Playgroud)
不使用同步或AtomicInteger将不是线程安全的,因为递增是复合操作.
为什么程序无法无限期运行?
那取决于各种情况.在大多数情况下,JVM足够智能来刷新内容.
正确使用volatile讨论了volatile的各种可能用途.正确使用volatile非常棘手,我会说"如果有疑问,请将其保留",请使用synchronized块.
也:
可以使用synchronized块代替volatile,但反之则不然.
Sok*_*lov 27
对于您的特定示例:如果未声明为volatile,则服务器JVM可以将keepRunning变量提升出循环,因为它未在循环中修改(将其转换为无限循环),但客户端JVM不会.这就是你看到不同结果的原因.
关于易变量的一般解释如下:
声明字段时volatile,编译器和运行时会注意到此变量是共享的,并且不应对其中的操作与其他内存操作重新排序.易失性变量不会缓存在寄存器或缓存中,而是隐藏在其他处理器中,因此读取volatile变量始终会返回任何线程的最新写入.
volatile变量的可见性效果超出了volatile变量本身的值.当线程A写入volatile变量并且随后线程B读取同一个变量时,在写入volatile变量之前A可见的所有变量的值在读取volatile变量后变为B可见
volatile变量最常见的用途是完成,中断或状态标志:
volatile boolean flag;
while (!flag) {
// do something untill flag is true
}
Run Code Online (Sandbox Code Playgroud)
易失性变量可用于其他类型的状态信息,但尝试此操作时需要更加小心.例如,volatile的语义不够强大,无法使increment(count++)原子化,除非您可以保证变量只从单个线程写入.
锁定可以保证可见性和原子性; volatile变量只能保证可见性.
仅当满足以下所有条件时,才能使用volatile变量:
调试提示:确保在调用JVM时始终指定-server JVM命令行开关,即使是用于开发和测试.服务器JVM执行比客户端JVM更多的优化,例如从循环中提取变量而不在循环中修改; 可能看起来在开发环境(客户端JVM)中工作的代码可能在部署环境(服务器JVM)中中断.
这是摘自Java Concurrency in Practice,这是您可以找到关于此主题的最佳书籍.
小智 14
我稍微修改了你的例子.现在使用keepRunning作为volatile和non volatile成员的示例:
class TestVolatile extends Thread{
//volatile
boolean keepRunning = true;
public void run() {
long count=0;
while (keepRunning) {
count++;
}
System.out.println("Thread terminated." + count);
}
public static void main(String[] args) throws InterruptedException {
TestVolatile t = new TestVolatile();
t.start();
Thread.sleep(1000);
System.out.println("after sleeping in main");
t.keepRunning = false;
t.join();
System.out.println("keepRunning set to " + t.keepRunning);
}
}
Run Code Online (Sandbox Code Playgroud)
Pra*_*shi 12
什么是volatile关键字?
volatile关键字阻止
caching of variables.
考虑代码,首先没有volatile关键字
class MyThread extends Thread {
private boolean running = true; //non-volatile keyword
public void run() {
while (running) {
System.out.println("hello");
}
}
public void shutdown() {
running = false;
}
}
public class Main {
public static void main(String[] args) {
MyThread obj = new MyThread();
obj.start();
Scanner input = new Scanner(System.in);
input.nextLine();
obj.shutdown();
}
}
Run Code Online (Sandbox Code Playgroud)
理想情况下,这个程序应该print hello直到RETURN key被按下.但是,some machines可能会发生变量运行,cached并且您无法从shutdown()方法更改其值,从而导致infinite打印hello文本.
因此,使用volatile关键字,guaranteed您的变量将不会被缓存,即将run fine打开all machines.
private volatile boolean running = true; //volatile keyword
Run Code Online (Sandbox Code Playgroud)
因此使用volatile关键字是good和safer programming practice.
理想情况下,如果keepRunning不可变,则线程应无限期继续运行。但是,它会在几秒钟后停止。
如果您在单处理器中运行,或者系统非常忙,则操作系统可能会换出线程,从而导致某些级别的缓存失效。没有a volatile并不意味着将不会共享内存,但是JVM出于性能原因会尝试不同步内存(如果可以的话),因此可能不会更新内存。
要注意的另一件事System.out.println(...)是同步,因为基础进行PrintStream同步以停止重叠的输出。因此,您可以在主线程中“免费”获得内存同步。但是,这仍然不能解释为什么阅读循环完全看到更新。
无论println(...)是输入输出还是输出输出,您的程序都可以在配备Intel i7的MacBook Pro上以Java6操作系统为我旋转。
谁能用例子解释易失性?不符合JLS的理论。
我认为您的榜样很好。不知道为什么它不能System.out.println(...)删除所有语句。这个对我有用。
易失性可以代替同步吗?它能达到原子性吗?
在内存同步方面,volatile抛出的内存屏障与synchronized块相同,除了volatile屏障是单向的还是双向的。 volatile读引发负载障碍,写引发存储障碍。阿synchronized块是双向屏障通过加入互斥锁定。
在方面atomicity,然而,答案是“看情况”。如果要从字段读取或写入值,则volatile可以提供适当的原子性。但是,增加volatile字段的限制++实际上是3个操作:读取,递增,写入。在那种情况或更复杂的互斥情况下,synchronized可能需要一个完整的块。 AtomicInteger通过++复杂的测试和设置自旋环解决了该问题。
Variable Volatile:易变关键字适用于变量。Java中的volatile关键字保证volatile变量的值始终从主内存中读取,而不是从Thread的本地缓存中读取。
Access_Modifier volatile DataType Variable_Name;
Run Code Online (Sandbox Code Playgroud)
易失字段:向VM指示多个线程可能尝试同时访问/更新该字段的值。对于一种特殊的实例变量,必须在所有具有修改后值的线程之间共享。与Static(Class)变量类似,主内存中仅缓存了一个易失值副本,因此在执行任何ALU操作之前,每个线程必须在ALU操作之后从主内存中读取更新后的值,然后才必须写入主内存位置。(对易失性变量v的写入将与任何线程对v的所有后续后续读取进行同步),这意味着对易失性变量的更改始终对其他线程可见。
这里一个nonvoltaile variable,如果线程T1变化T1的缓存值,线程T2不能访问更改后的值,直到T1写入,T2从主内存中读取最新修改的值,这可能会导致Data-Inconsistancy。
Run Code Online (Sandbox Code Playgroud)+--------------+--------+-------------------------------------+ | Flag Name | Value | Interpretation | +--------------+--------+-------------------------------------+ | ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached.| +--------------+--------+-------------------------------------+ |ACC_TRANSIENT | 0x0080 | Declared transient; not written or | | | | read by a persistent object manager.| +--------------+--------+-------------------------------------+
Shared Variables:可以在线程之间共享的内存称为共享内存或堆内存。所有实例字段,静态字段和数组元素都存储在堆内存中。
同步:同步适用于方法,块。一次只能在对象上执行1个线程。如果t1取得控制权,则其余线程必须等待直到它释放控制权。
例:
Access_Modifier volatile DataType Variable_Name;
Run Code Online (Sandbox Code Playgroud)
静态[
Class Field] vs挥发性[Instance Field]-两者都不被线程缓存
静态字段是所有线程共有的,并存储在“方法区域”中。静态与挥发性无用。静态字段无法序列化。
易失性主要与实例变量一起使用,该实例变量存储在堆区域中。volatile的主要用途是维护所有线程的更新值。实例volatile字段可以序列化。
@看到
| 归档时间: |
|
| 查看次数: |
70969 次 |
| 最近记录: |