gpt4 book ai didi

java - 为什么变量在同步时对其他线程不可见?

转载 作者:塔克拉玛干 更新时间:2023-11-03 04:17:16 25 4
gpt4 key购买 nike

假设我有两个线程 t1t2 正在尝试访问 incX()

下面是我的代码:

    class Test implements Runnable {
private int x = 0;

public void incX() {
synchronized(this) {
x = ++x;
}
System.out.println("x is: "+x+" "+Thread.currentThread().getName());
}

public void run() {
incX();
}

public static void main(String[] args) {

Thread t1 = new Thread(new Test());
t1.start();
Thread t2 = new Thread(new Test());
t2.start();

}

这是我的输出:

    x is: 1     Thread-1
x is: 1 Thread-0

incX() 方法中,我已经同步了 x =++x,所以对线程 t1 所做的更改应该是可见的线程t2,对吗?所以我的输出应该是:

    x is: 1     Thread-1
x is: 2 Thread-0

我知道 ++x 不是原子操作,但它是同步的,所以线程 t2 无法获取锁。所以线程不应该交错并且对x所做的更改应该对线程t2可见,对吗?我误会了吗?所以我的问题是为什么我没有得到输出:

     x is: 2     Thread-0

最佳答案

您正在使用 Test 类的两个独立实例,因此每个实例中的 x 自然只会递增一次。它实际上与此相同:

Test test1 = new Test();
Test test2 = new Test();
test1.incX();
test2.incX();

由于 Test 的每个实例都有自己的 x,您也会看到 1 两次以及该代码。

要测试对相同 实例的同步访问,您需要改用单个实例。例如:

class Test {
private int x = 0;

public void incX() {
synchronized(this) {
x = ++x; // See "Side Note" below
}
System.out.println("x is: "+x+" "+Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
Test test = new Test(); // One instance
Thread t1 = new Thread(() -> {
test.incX(); // Used by this thread
});
Thread t2 = new Thread(() -> {
test.incX(); // And also by this one
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
}
catch (Exception e) {
}
System.out.println("Done");
}
}

哪个会输出

x is: 1     Thread-0x is: 2     Thread-1Done

or similar (naturally it varies depending on which thread gets scheduled when).

Sometimes, it'll even look like this:

x is: 2     Thread-0x is: 2     Thread-1Done

That's because the access to x in the System.out.println statement is outside the synchronized block, so sometimes (not always) x will get incremented after the end of the synchronized block and before the println:

synchronized(this) {
x = ++x;
}
// ***The other thread can jump in here and increment x
System.out.println("x is: "+x+" "+Thread.currentThread().getName());

更详细:

  1. t1进入同步块(synchronized block)
  2. t2 尝试进入同步块(synchronized block)但必须等待,因为 t1 有锁
  3. t1 递增 x,使其成为 1
  4. t1 退出同步块(synchronized block)
  5. t2 跳入并递增 x,使其成为 2
  6. t2 退出同步块(synchronized block)
  7. t1 输出 x (2) 的当前
  8. t2 输出 x (2) 的当前

请注意,第 2 步和第 3 步可以按任何顺序排列,第 6-8 步也可以按任何顺序排列。

为了可靠地报告 x 在递增后在同步块(synchronized block)中的情况,我们想要:

  1. println移入同步块(synchronized block)

    public void incX() {
    synchronized(this) {
    x = ++x; // See "Side Note" below
    System.out.println("x is: "+x+" "+Thread.currentThread().getName());
    }
    }

  2. 将自增的结果保存在局部变量中

    public void incX() {
    int y; // Local variable
    synchronized(this) {
    y = ++x; // No longer and odd thing to do
    }
    System.out.println("x is: "+y+" "+Thread.currentThread().getName());
    }

除非你有一个非常非常好的理由在输出期间持有同步锁,否则请使用#2。


旁注:正如我在评论中提到的,++x 已经 将其值写回到 x,这就是增量运算符做。所以 x = 的一部分

x = ++x;

...没有必要。要么使用增量运算符:

++x;

...或者不:

x += 1;

关于java - 为什么变量在同步时对其他线程不可见?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39901528/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com