您的位置:首页 > 编程语言 > Java开发

对java中volatile关键字的理解

2016-10-14 00:00 239 查看
volatile关键字的作用:保证变量的可见性

一定要谨记
volatile
关键字在 Java 代码中仅仅保证这个变量是可见的:它不保证原子性。在那些非原子且可由多个线程访问的易变操作中,一定不能够依赖于 volatile 的同步机制。相反,要使用
java.util.concurrent
包的同步语句、锁类和原子类。它们在设计上能够保证程序是线程安全的。

volatile作用的具体描述

Java如何保证可见性

Java提供了
volatile
关键字来保证可见性。当使用volatile修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它缓存中对该变量的缓存设置成无效,因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。

引自- wangzzu的博客文章

上面这段话能十分清楚的说明这个变量的用处了吧。下面配合代码更加准确的验证这个问题。

代码引自-- 阿里工程师oldratlee的github

Demo类
com.oldratlee.fucking.concurrency.NoPublishDemo


Demo说明

主线程中设置属性
stop
true
,以控制在
main
启动的任务线程退出。

问题说明

在主线程属性
stop
true
后,但任务线程持续运行,即任务线程中一直没有读到新值。

出现上述问题说明的代码:

public class NoPublishDemo {
boolean stop = false;

public static void main(String[] args) {
// LoadMaker.makeLoad();

NoPublishDemo demo = new NoPublishDemo();

Thread thread = new Thread(demo.getConcurrencyCheckTask());
thread.start();

Utils.sleep(1000);
System.out.println("Set stop to true in main!");
demo.stop = true;
System.out.println("Exit main.");
}

ConcurrencyCheckTask getConcurrencyCheckTask() {
return new ConcurrencyCheckTask();
}

private class ConcurrencyCheckTask implements Runnable {
@Override
public void run() {
System.out.println("ConcurrencyCheckTask started!");
// 如果主线中stop的值可见,则循环会退出。
// 在我的开发机上,几乎必现循环不退出!(简单安全的解法:在running属性上加上volatile)
while (!stop) {
}
System.out.println("ConcurrencyCheckTask stopped!");
}
}
}

例子中主线程的stop变量没有做任何的显示定义volatile。

当我们执行后的打印结果为:

ConcurrencyCheckTask started!
Set stop to true in main!
Exit main.

并没有打印

ConcurrencyCheckTask stopped!

并且程序还没有结束。

接下来我们对stop变量添加volatile

volatile boolean stop = false;

程序完美结束。也印证了上文描述中的内容。

组合状态读到无效组合

--此时使用volatile则无法解决同时修改两个值,数据还是返回正确的问题。

意思是同时对两个值修改,但是实际返回确实错误的。

程序设计时,会需要多个状态记录(状态可以是个
POJO
对象或是
int
等等)。常看到多状态读写没有同步的代码,并且写的同学会很自然地就忽略了线程安全的问题。

无效组合 是指 从来没有设置过的组合。

Demo说明

主线程修改多个状态,为了方便检查,每次写入有个固定的关系:第2个状态是第1个状态值的2倍。在任务线程中读取多个状态。
Demo类
com.oldratlee.fucking.concurrency.InvalidCombinationStateDemo


问题说明

任务线程中读到了 第2个状态不是第1个状态值2倍的值,即是无效值。

快速运行

mvn compile exec:java -Dexec.mainClass=com.oldratlee.fucking.concurrency.InvalidCombinationStateDemo

实际测试代码

public class InvalidCombinationStateDemo {
public static void main(String[] args) {
CombinationStatTask task = new CombinationStatTask();
Thread thread = new Thread(task);
thread.start();

Random random = new Random();
while (true) {
int rand = random.nextInt(1000);
task.state1 = rand;
task.state2 = rand * 2;
}
}

private static class CombinationStatTask implements Runnable {
// 对于组合状态,加 volatile 不能解决问题
volatile int state1;
volatile int state2;

@Override
public void run() {
int c = 0;
for (long i = 0; ; i++) {
int i1 = state1;
int i2 = state2;
if (i1 * 2 != i2) {
c++;
System.err.printf("Fuck! Got invalid CombinationStat!! check time=%s, happen time=%s(%s%%), count value=%s|%s\n",
i + 1, c, (float) c / (i + 1) * 100, i1, i2);
} else {
// 如果去掉这个输出,则在我的开发机上,发生无效组合的概率由 ~5% 降到 ~0.1%
System.out.printf("Emm... %s|%s\n", i1, i2);
}
}
}
}

}


long
变量读到无效值

无效值 是指 从来没有设置过的值。

long
变量读写不是原子的,会分为2次4字节操作。
Demo类
com.oldratlee.fucking.concurrency.InvalidLongDemo


Demo说明

主线程修改
long
变量,为了方便检查,每次写入的
long
值的高4字节和低4字节是一样的。在任务线程中读取
long
变量。

问题说明

任务线程中读到了高4字节和低4字节不一样的
long
变量,即是无效值。

快速运行

mvn compile exec:java -Dexec.mainClass=com.oldratlee.fucking.concurrency.InvalidLongDemo

DEMO:

public class InvalidLongDemo {
long count = 0;

public static void main(String[] args) {
// LoadMaker.makeLoad();

InvalidLongDemo demo = new InvalidLongDemo();

Thread thread = new Thread(demo.getConcurrencyCheckTask());
thread.start();

for (int i = 0; ; i++) {
long l = i;
demo.count = l << 32 | l;
}
}

ConcurrencyCheckTask getConcurrencyCheckTask() {
return new ConcurrencyCheckTask();
}

private class ConcurrencyCheckTask implements Runnable {
@Override
public void run() {
int c = 0;
for (int i = 0; ; i++) {
long l = count;
long high = l >>> 32;
long low = l & 0xFFFFFFFFL;
if (high != low) {
c++;
System.err.printf("Fuck! Got invalid long!! check time=%s, happen time=%s(%s%%), count value=%s|%s\n",
i + 1, c, (float) c / (i + 1) * 100, high, low);
} else {
// 如果去掉这个输出,则在我的开发机上没有观察到invalid long
System.out.printf("Emm... %s|%s\n", high, low);
}
}
}
}

}

对于单个变量的值修改 给变量添加volatile也是可以解决此问题的。

十分感谢

Matt's Blog

李鼎(哲良)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java volatile