【问题标题】:Share local variable value between barrier threads in java在java中的屏障线程之间共享局部变量值
【发布时间】:2021-12-11 06:55:31
【问题描述】:

我一直致力于实现一个自定义的循环障碍,它将传递给 await 方法的值相加,并在调用 notify 后将总和返回给所有线程。

代码:

public class Barrier {

    private final int parties;
    private int partiesArrived = 0;

    private volatile int sum = 0;
    private volatile int oldSum = 0;

    public Barrier(int parties) {
        if (parties < 1) throw new IllegalArgumentException("Number of parties has to be 1 or higher.");
        this.parties = parties;
    }

    public int getParties() { return parties; }

    public synchronized int waitBarrier(int value) throws InterruptedException {
        partiesArrived += 1;
        sum += value;
        if (partiesArrived != parties) {
            wait();
        }
        else {
            oldSum = sum;
            sum = 0;
            partiesArrived = 0;
            notifyAll();
        }
        return oldSum;
    }

    public int getNumberWaiting() { return partiesArrived; }
}

这可行,但我听说有一种方法可以将值sumoldSum(或至少oldSum)更改为waitBarrier 方法的局部变量。然而,在我绞尽脑汁之后,我没有看到任何方法。

有没有可能,如果有,怎么做?

【问题讨论】:

  • oldSum,当然。但是sum 怎么可能是一个局部变量呢?线程不能共享局部变量。如果线程不相互共享,它们如何计算总和?
  • @SolomonSlow,你能详细说明一下oldSum是如何变成局部变量的吗?
  • 嗯,...去掉oldSum的类级声明,在waitBarrier(...)函数内声明int oldSum
  • @SolomonSlow 不起作用。总和不会相同
  • 和什么一样?只有一笔。您只使用oldSum 作为临时存储来保存它,以便您可以在返回之前的值之前设置sum = 0

标签: java concurrency barrier shared-variable


【解决方案1】:

您可以返回sum 并让第一方清除它:

public synchronized int waitBarrier(int value) throws InterruptedException {
    if (partiesArrived == 0) {
        sum = 0;
    }

    partiesArrived++;
    sum += value;

    if (partiesArrived == parties) {
        notifyAll();
    } else {
        while (partiesArrived < parties) {
            wait();
        }
    }

    return sum;
}

请注意,对于spurious wakeups,应始终在循环中检查等待条件。此外,sum 不需要是 volatile,如果它不在 synchronized 块之外访问。

【讨论】:

  • 你说的是真的,但它没有回答 OP 的问题,即“如果 sum 是一个局部变量,这怎么能工作?”
  • @SolomonSlow (或至少oldSum
【解决方案2】:

然而,在我绞尽脑汁之后,我看不到路。

确实如此。

有没有可能,如果有,怎么做?

这是不可能的。

为了一些证据:

尝试将本地变量标记为volatile。它不起作用:编译器不允许这样做。 为什么不呢?因为 volatile 必然是无操作的:本地变量 不能与其他线程共享

有人可能会认为这是“分享”本地人:

void test() {
   int aLocalVar = 10;
   Thread t = new Thread(() -> {
     System.out.println("Wow, we're sharing it! " + aLocalVar);
   });
   t.start();
}

但它是一些语法糖把你绊倒在那里:实际上(你可以用javap -c -v 确认这一点,以显示javac 为这段代码生成的字节码),本地的副本 var 被交给这里的块。这就解释了为什么在java中,上面的代码编译失败除非你试图共享的变量是[A]标记final或[B]可能被如此标记而没有错误(这被称为“变量实际上是最终的”)。如果 java 允许您像这样访问非(有效)决赛,并且 java 使用可用的复制机制,那将令人难以置信的混乱。

当然,在 java 中,所有的非基元都是引用。指针,用其他一些语言的说法。因此,您可以“共享”(不是真的,它将是一个副本)一个本地变量,但仍然可以得到您想要的(在 2 个线程之间共享状态),因为当您获得变量的副本时,该变量只是一个指针。就像这样:如果我有一张纸,它是我的,但我可以把它扔进复印机,也给你一份,我们似乎不能分享状态。我在纸上划的任何东西都不会神奇地出现在你的纸上;这不是巫毒纸。但是,如果我的纸上有房子的地址,我把它抄下来递给你一份,感觉就像我们在分享:如果你走到房子里,我不知道,把一块砖头扔进窗户,然后我走过去,我可以看到它。

java 中的许多对象是不可变的(砖块无法穿透),并且原语不是引用。一种解决方案是使用 AtomicX 系列,它们只是对原语或引用的简单包装,使它们可变:

AtomicInteger v = new AtomicInteger();
Thread t = new Thread(() -> {v.set(10);});
t.start();
t.yield();
System.out.println(t.get());
// prints 10

但这里并没有实际分享本地人。该线程获得了对位于堆上的单个 AtomicInteger 实例的引用的副本,并且两个线程最终都“走到了房子”,在这里。

【讨论】:

    猜你喜欢
    • 2014-04-27
    • 2021-08-14
    • 2019-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多