前言:
最近在整理多线程的一些用法,其中有涉及到 volatile 关键字,没有深入研究,只是简单的验证了一下,这里记录一下心得。
一. volatile 是轻量级的
为啥一定要强调轻量级?
1. volatile 只能修饰变量
2. 保证不同线程操作时变量的可见性,即一个线程修改了变量,另一个线程立马可见
3. 不能确保线程同步(这个下面会举例说明)
二. 变量可见性验证
几乎所有的博客都会举下面这个例子说明
public class VolatileTest extends Thread {
boolean flag = false;
int i = 0;
public void run() {
while (!flag) { // 如果为true会马上结束循环
i++;
}
}
public static void main(String[] args) throws Exception {
VolatileTest vt = new VolatileTest();
vt.start();
Thread.sleep(10);
System.out.println(vt.isAlive());
vt.flag = true; //设置为true,意图停止循环
System.out.println("store:" + vt.i);
Thread.sleep(10);
System.out.println(vt.isAlive());
}
}
打印一下结果:
true
store:7517191
true
进程没有结束,并且陷入死循环,跟想要的结果相去甚远。
这里其实涉及到java内存模型的问题,有点复杂,这里简单说一下结论:
上述代码中的main主线程启动了 vt 线程, vt 线程启动后设置 flag为 false,vt 线程在执行的时候flag值是不会自动更新的(vt.flag 就没有意义了),所以flag一直是false。
public class VolatileTest extends Thread {
volatile boolean flag = false;
int i = 0;
public void run() {
while (!flag) { // 如果为true会马上结束循环
i++;
}
}
public static void main(String[] args) throws Exception {
VolatileTest vt = new VolatileTest();
vt.start();
Thread.sleep(10);
System.out.println(vt.isAlive());
vt.flag = true; //设置为true,意图停止循环
System.out.println("store:" + vt.i);
Thread.sleep(10);
System.out.println(vt.isAlive());
}
}
加上 volatile 关键字以后,线程在使用 flag 的时候会主动去更新值,这样 vt.flag = true 就会生效,vt 线程就退出循环逐步结束,输出结果如下
true
store:7537387
false
三. volatile 不能确保线程同步
大家可能会觉得,既然 volatile 可以保证变量是及时可见的,那为啥又不能保证线程同步?别急,这里用事实说话
public class VolatileTestSyn {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final VolatileTestSyn test = new VolatileTestSyn();
for (int i = 0; i < 5; i++) {
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++)
test.increase();
}
}.start();
}
while (Thread.activeCount() > 2) //保证前面的线程都执行完
Thread.yield();
System.out.println("result:"+test.inc);
}
}
打印结果(机器性能高的话可能还是5000,可以适当调大循环次数或线程个数):
4658
如果线程是同步的,那结果肯定是5000没跑,结果却不是,为啥?
多个线程同时操作 inc,每次取到的 inc 都是计算后写到内存的最新值,按道理不应该出错;但是,假如 A 线程启动取得的 inc 是100,这时候 B 线程也启动取得 inc 100;A 线程执行 +1 操作把 101 写到内存,但是这时候 B 线程短暂暂停(注意,B线程已经取得 inc 为 100,B线程不会再更新 inc 了); C 线程启动获取 inc 为101,执行 +1 操作,并把102 写到内存,而后很快 B 线程也执行了 +1 操作,把 101 写到内存覆盖了 C线程的结果;这时候 D线程启动获取的 inc 是 101……..这就解释了为啥上面结果不是 5000。(这块涉及 Java 内存模型,想深究的同学可以研究一下)
所以这时候就需要给 increase() 方法加一个 synchronized ,使得方法在多线程单一持有,再也不会出现上面的问题了。
四. 参考博客
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/16523.html