Java 易失性和多线程:以下程序的所有可能输出是什么?

问题描述 投票:0回答:1

考虑以下代码:

public class Main {
    
    public static void main(String args[]){ 
        Thread t1 = new SimpleThread();
        Thread t2 = new SimpleThread();
        t1.start();
        t2.start();
    }
    
    static class SimpleThread extends Thread {
        
        private static volatile int n = 0;
        
        public void run() {
            n++;
            int m = n;
            System.out.println(m);
        }
    }
}

该程序所有可能的输出是什么?为什么?

我想到了以下场景:

  • t1 和 t2 完全执行(无论顺序如何),而不是并行执行,所以我们有
    1 2
  • t1 和 t2 并行执行,在两个线程中
    n++
    都“正确”执行,但调度程序首先执行具有
    m=2
    的线程的打印,所以我们有
    2 1
  • t1 和 t2 并行执行,并且都在指令
    n=0
    中读取
    n++
    ,所以我们有
    1 1

我对这些场景的理解正确吗?是否有一些我错过的场景(例如,

2 2
是否会发生以及为什么) ?

java multithreading output volatile
1个回答
0
投票

++
增量运算符被视为非原子

你的代码

n++
不是原子的。或者说,我们必须假设 Java 中 Postfix 增量运算符
++
的定义。请参阅 Java 语言规范,其中没有做出线程安全原子性 的承诺。另请参阅 为什么 i++ 不是原子的?

所以时间上有三个时刻:

  • n
    读取当前值。 (本质上是复制该值)
  • 1
    添加到该值。
  • 将总和存储回变量中。

这些时刻之间可能会经过任意时间。在此期间,另一个线程可以访问和/或更改变量内的值。因此第一个线程可以践踏第二个线程设置的值。

volatile
保证可见性,但不保证线程安全

private static volatile int n = 0;

那里的

static
表示应用程序中仅存在一个名为
n
的变量。

 n++;
&
int m = n;

这两条线之间可能会经过任意时间。在此期间,另一个线程可能会更改我们唯一的

n
变量的值。

顶部行中的

volatile
意味着当前线程将看到任何更改,保证。

因此,在这一对行的第一行中设置的值可能与第二行中访问的值很不同。

场景

至于您的场景:

  • 是的,如果一个线程在另一个线程开始之前完成,您将得到
    1
    &
    2
    作为输出。
  • 如果线程在单独的内核上同时运行,或者如果它们在单个内核上交替运行,则它们的操作可能会交错。

如果我们结合上面讨论的事实:

  • 增量运算符是非原子的
  • 递增线和
    m = n
    线是非原子的

…然后我们有四个与

n
相关的步骤的序列,这些步骤可能会交错:

  1. 读取
    n
    的值,制作副本。
  2. 增加复制值。
  3. 将增量复制值写入变量
    n
  4. 读取变量
    n
    中的值(复制,存储在
    m
    中)。

交错场景包括:

  • 1
    1

    两个线程都从变量中读取
    0
    ,并将
    1
    写入变量。第二个作家践踏了第一个作家。控制台输出为
    1
    &
    1
  • 1
    &
    2
    or
    2
    &
    1

    一个线程读取
    0
    ,写入
    1
    ,然后读取
    1
    。另一个线程读取
    1
    并写入
    2
    ,然后读取
    2
    。没有踩踏,因为读写是连续的。但请注意,在这种情况下,连续的
    println
    调用可能不是连续的。
    println
    调用可能会交错。因此控制台输出可能按以下任一顺序:
    1
    &
    2
    2
    &
    1
  • 2
    &
    2

    一个线程读取
    0
    ,并写入
    1
    ...,但其他线程读取
    1
    并写入
    2
    ...,第一个线程读取
    2
    。第一个线程写入了
    1
    ,但在为
    n
    赋值检索
    m = n
    的当前值之前,
    n
    中的值已被另一个线程更改。

您的问题中未涵盖

2
2
的最后一个案例。感谢 Mark Rotteveel 在评论中指出了这个案例。花了点时间才看到!


警告:我不是这方面的专家。如需专业知识,请参阅《Java 并发实践》一书

© www.soinside.com 2019 - 2024. All rights reserved.