无法正确使用volatile关键字

3

我在使用volatile关键字和不使用时得到了相同的输出。

我在代码中犯了什么错误吗?

public class MutliThreadExample extends Thread{

    volatile int value;
    
    public static void main(String[] args) throws InterruptedException {
        MutliThreadExample thread1 = new MutliThreadExample();
        MutliThreadExample thread2 = new MutliThreadExample();
        thread1.start();
        thread1.join();
        thread2.start();
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            value = value + 1;
            System.out.println(value + "-" + Thread.currentThread());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

我无法正确地在Java中使用volatile关键字。


3
"value = value + 1" 需要一个 AtomicInteger,而不仅仅是一个 volatile。 - Louis Wasserman
2
你好,欢迎!:) 也许...但是:thread1.start(); thread1.join(); thread2.start(); 是顺序执行的(即不是并行的 - xerx593
2
volatile 使读写操作具有原子性,但它们的组合不具备原子性。例如,请参见 https://www.scaler.com/topics/volatile-keyword-in-java/。对 volatile 变量进行递增操作是 安全的,需要使用完整的 AtomicInteger - Louis Wasserman
@Louis Wasserman volatile 不是关于原子性的,请不要混淆其他人:https://dev59.com/JmIj5IYBdhLWcg3w8JRZ:~:text=Volatile%20and%20Atomic%20are%20two,on%20variables%20are%20performed%20atomically. - Mikhail2048
2
每个 MutliThreadExample 实例都有自己的 value,因此只有一个线程访问它;对于你的代码来说,volatile 真的不是必需的。 - Mark Rotteveel
显示剩余4条评论
1个回答

7
我在这里做错了什么吗?
是的,有多个问题。
首先,由于以下原因,您的应用程序实际上是单线程(而不是多线程):
    thread1.start();
    thread1.join();
    thread2.start();

那段话的意思是

  1. 开始线程1
  2. 等待线程1完成
  3. 开始线程2

因此,线程1和线程2不会同时运行。

其次,这些线程会执行以下操作:

    Thread.sleep(1000);

当您调用sleep时,缓存在寄存器中的变量值等可能会被写入主内存。这有可能使此代码的行为不同,并干扰您尝试进行的任何观测。

第三点,他们这样做:

    System.out.println(value + "-" + Thread.currentThread());

println调用中,I/O堆栈通常在幕后进行一些同步,也会干扰对value的观察。此外,value自增后可能已经发生了变化。
第四点,您可以像这样增加变量:
    value = value + 1;

即使将value声明为volatile,这也不是原子操作。实际上,它是一个读取操作,接着是加法操作,最后是写入操作。如果您编写以下代码,则情况也是如此。

    value++;  // or ++value;

如果两个线程实际上同时执行这些语句,由于读取、添加和写入操作的交错,它们可能会丢失一个递增。

(将变量声明为volatile在一个线程写入变量之后,其他线程对同一变量的后续读取之间建立了一个“happens before”关系。但这还不足以以线程安全的方式实现可以被两个或多个线程更新的计数器。为了做到这一点,您需要使用synchronizedAtomicInteger或等效物。)

最后,您将value声明为MutliThreadExample的实例变量,而thread1thread2是该类的不同实例。这意味着thread1thread2正在更新不同的value变量。


总之,你的代码在两个版本中产生相同的输出,因为它不是多线程的。 sleepprintln 调用也可能会干扰“观察结果”。而且这些线程正在更新不同的value变量。

如果你解决了所有这些问题,你的代码仍然不是线程安全的,无论是否使用volatile。 两个版本都不能保证产生正确或错误的答案。 它们都具有非确定性行为。

可靠地展示volatile的效果是困难的。 做基本的编程错误也没有帮助。


1
"value" 不是共享的:每个线程都有其自己的该字段实例,因此在此代码中,volatile 实际上并没有起到作用。 - Mark Rotteveel
1
天啊!我错过了。 - Stephen C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接