Java 的 volatile:能否在只使用 volatile 不使用更高级多线程安全措施的前提下,让这段代码按预期工作?

2021-04-24 22:58:47 +08:00
 Newyorkcity
public class VolatileTest {

    private volatile static int a = 0;
    private static int cnt = 0;

    public static void test() {
        if (a == 0) {
            a = 1;
            cnt++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[2];
        for (int i = 0; i < 10000; i++) {
            for (int j = 0; j < 2; j++) {
                threads[j] = new Thread(VolatileTest::test);
            }
            for (Thread thread : threads) {
                thread.start();
            }
            for (Thread thread : threads) {
                thread.join();
            }
            if (cnt == 2) {
                System.out.println("false!");
            }
            a = 0;
            cnt = 0;
        }
        System.out.println("finish");
    }
}

在反复启动程序的尝试中,一般出现的情况是控制台出现一次 false!,偶尔会出现两次或者不出现。

希望能达成 false!永远不会被输出,即 cnt 结束时总为 1 的结果,能否在不引入 Atomic*类,synchrnozied,加锁等更高级工具,只使用 volatile 实现?

cnt ++ 代表着希望被执行一次的代码。可能会有多个线程调用 VolatileTest.test(),但 cnt++只执行一次。

谢谢

1346 次点击
所在节点    问与答
17 条回复
geelaw
2021-04-24 23:38:36 +08:00
不能。
Newyorkcity
2021-04-24 23:43:07 +08:00
@geelaw 那 volatile 的保持可见性,以及一些文章提到 volatile boolean 可能靠谱的情况是什么呢。。
Jooooooooo
2021-04-24 23:49:14 +08:00
当然不行, 两个线程会同时运行到 if(a==0) 并且发现 a 确实都是 0
Newyorkcity
2021-04-24 23:50:04 +08:00
@geelaw 是因为 voliatle 永远无法保证 『线程 b 执行完 if a == 0 』这件事不发生在 『线程 a 执行完 if a == 0 』和『线程 a 执行 a = 1 』之间吗?
Leviathann
2021-04-25 01:12:38 +08:00
借楼问一下
我看项目里以前的代码有用 volatile 修饰一个 service 里的 map,而 map 除了初始化有个赋值为 new hashmap,其他都只有 get set
这种 volatile 有什么用? volatile 应该只涉及到 map 的这个引用不涉及到内部元素的吧
还是我理解错了
cubecube
2021-04-25 01:57:34 +08:00
@Leviathann 你理解得没错,除非会并发访问并存在更新 map 本身为另外一个 hashmap or null,正常 map 没必要加
EscYezi
2021-04-25 09:08:32 +08:00
@Newyorkcity #4 是的,因为判断 a 是否为 0 和给 a 赋值 1 是两个操作,想要达到预期的效果必须把这个两个操作变成一个原子操作
bxb100
2021-04-25 09:57:26 +08:00
操作和可见不是一件事情吧
eric96
2021-04-25 09:57:32 +08:00
两个线程同时判断到 a==0,然后进入条件语句,将 a 赋值为 1.volatile 只是保证了可见性和不重排,但是你对 a 的判断和赋值是两个操作,不是原子的
securityCoding
2021-04-25 10:02:42 +08:00
@Leviathann 这是在瞎用了,volatile 一般结合 cas 实现无锁并发读写。
eric96
2021-04-25 10:07:03 +08:00
要么加锁,要么用 Atomic 。其实可以看下 Atomic 的实现,也就是 volatile 加上 cas 操作
dqzcwxb
2021-04-25 10:38:30 +08:00
@eric96 #11 也可以用 LongAdder
theOneMe
2021-04-25 11:41:35 +08:00
多于两步的操作,要么无锁同步加可见行,要么加锁。
inhzus
2021-04-25 13:04:01 +08:00
有 自己用 volatile 实现一遍 cas,也挺简单的 /doge
wqhui
2021-04-25 13:52:50 +08:00
没记错的话 volatile 只是每次去内存读值,不使用缓存,保证每次读到的值是最新的。但是运气不好的话,一个线程读到了 a=0,在做 a=1 的赋值操作前这段时间,其他线程也是能够进来的
wolfie
2021-04-25 13:53:42 +08:00
要是能实现,Atomic* 情何以堪。
LukeChien
2021-04-25 18:26:31 +08:00
如果你希望原子执行的代码都如 cnt++ 这么简单,那你可以换个单核的 CPU :)

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/772992

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX