【问题标题】:Two threads referring one variable两个线程引用一个变量
【发布时间】:2017-02-28 14:33:49
【问题描述】:

我正在运行以下课程。

public class RunThreads implements Runnable {
    static int  i;
    public static void main(String[] args) {
        RunThreads job = new RunThreads();
        Thread alpha = new Thread(job);
        Thread beta = new Thread(job);
        alpha.setName("Alpha");
        beta.setName("beta");
        alpha.start();
        beta.start();
    }
    public void run(){
        for(;i<10;i++){
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

我的输出是:

beta0
beta1
Alpha0
beta2
beta4
beta5
beta6
Alpha3
Alpha8
beta7
Alpha9

我知道每次执行它都会得到不同的输出。我的问题是,对于alphabeta 线程,即Alpha0beta0,为什么输出的i 值两次为0

i 的值已被beta 线程增加到1。那么,alpha 线程如何打印出Alpha0

我可能在这里遗漏了一些非常明显的东西。谢谢!

【问题讨论】:

  • 您的线程正在竞速。他们都试图读取和更新同一个变量,并且无法保证操作将以什么顺序发生。请参阅en.wikipedia.org/wiki/Race_condition#Example
  • @khelwood:哎呀,错过了 :)
  • @JonSkeet ,i 变量是静态的,所以线程在这里共享变量,对吧?
  • 答案是:如果你想这样做,请使用 AtomicInteger :)
  • 是的,我错过了 - 没有仔细阅读这个问题。道歉。

标签: java multithreading java-threads


【解决方案1】:

当您访问没有同步等的共享数据时,事情会很可怕:

  • 无法保证读取 i 的 Alpha 线程会看到来自 beta 线程的“最新”更新,反之亦然
  • 两个线程可以大致同时启动i++...而i++ 基本上是:

    int tmp = i;
    tmp++;
    i = tmp;
    

    你很容易“失去”增量

如果你想让这个线程安全,你应该改用AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;

public class RunThreads implements Runnable {
    static AtomicInteger counter = new AtomicInteger();
    public static void main(String[] args) {
        RunThreads job = new RunThreads();
        Thread alpha = new Thread(job);
        Thread beta = new Thread(job);
        alpha.setName("Alpha");
        beta.setName("beta");
        alpha.start();
        beta.start();
    }
    public void run(){
        int local;
        while ((local = counter.getAndIncrement()) < 10) {
            System.out.println(Thread.currentThread().getName() + local);
        }
    }
}

您的输出可能仍然显示错误顺序(因为 alpha 线程可能“开始”写入“Alpha0”,而 beta 线程“开始”写入“beta1”,但 beta 线程得到首先锁定控制台输出),但您只会看到每个计数一次。请注意,您必须同时使用 getAndIncrement() 的结果进行检查和打印 - 如果您在循环主体中调用 counter.get(),由于操作的交错,您仍然可以看到重复项。

【讨论】:

    【解决方案2】:

    这里的简单答案是,无论变量是 volatile 还是 atomic 或其他什么,您的两个线程都在变量值为 0 时启动,并且仅在打印后 更改它。

    这意味着两个线程都可以在其中任何一个到达i++之前到达“打印”行。

    这意味着两者都极有可能打印 0,除非其中一个延迟足够长的时间以使另一个更新变量(此时出现内存模型和数据可见性的问题)。

    【讨论】:

    • 如果打印了beta1,则表示i的值已经递增到1,所以当alpha中的print语句执行时,试图获取@987654326的值@ ,它如何得到0 来打印Alpha0
    • 好吧,@tarunkt,这就是“内存模型”问题的来源。您在一个线程中更改非同步变量这一事实并不意味着另一个线程会看到新值。它会看到自己的缓存值。
    【解决方案3】:

    “当”线程 A 进行更新时,线程 B 正在读取该值。

    喜欢:

    A: prints i (current value 0)
    B: prints i (current value 0)
    A: stores 1 to i
    

    你不能假设这两个动作:

    for(;i<10;i++){ // increasing the loop counter
    

    System.out.println(Thread.currentThread().getName() + i);
    

    打印循环计数器发生在“一次”中。从相反的方面来说。

    两个线程并行执行相应的指令;并且绝对不能保证结果。

    【讨论】:

    • 正确,这是误导;删除了那部分。
    【解决方案4】:

    线程可能在多核架构中的不同内核上运行,并且每个内核都有自己的注册表集或本地缓存,只要您不使缓存无效,运行的内核可能无法到达 RAM 内存以达到-日期值,并将使用缓存的。为了使其按预期工作,必须将变量标记为 volatile,这将使每次写入此内存位置时的缓存无效,并直接从 RAM 中获取值。

    更新: 正如在 cmets 中指出的那样 - volatile 不足以使值保持最新,i++ 上有一个读写操作。要使其工作,AtomicInteger 就足够了,或者锁定 i++ 的成本会稍微高一些。

    【讨论】:

    • 使用 volatile 变量只能解决一小部分问题。增量是非原子的,增量与获取是分开的,这无济于事。
    • @JonSkeet 我同意,你仍然会得到关于谁先写的竞争条件。您将获得 10-20 范围内的值,具体取决于线程是否在读取 i 操作和执行 ++ 操作后执行写入内存之间挤压。
    猜你喜欢
    • 2012-03-06
    • 2013-10-21
    • 1970-01-01
    • 2014-09-08
    • 2021-12-21
    • 2016-02-09
    • 2013-07-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多