简体   繁体   English

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

[英]Why variable is not visible to other thread in synchronization?

Let's assume I've two threads t1 and t2 which are trying to access incX() 我们假设我有两个线程t1t2试图访问incX()

Here is my following code: 这是我的以下代码:

    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();

}

Here's my output: 这是我的输出:

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

As in incX() method I've synchronized x = ++x , so the changes made to thread t1 should be visible to thread t2 , right? incX()方法中,我已经同步x = ++x ,因此对线程t1所做的更改应该对线程t2可见,对吧? So my output should be: 所以我的输出应该是:

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

I know ++x is not an atomic operation but it is synchronized, so thread t2 can't acquire the lock. 我知道++x不是原子操作但它是同步的,因此线程t2无法获取锁。 So the threads should not interleave and changes made to x should be visible to thread t2 , right? 所以线程不应该interleave ,对x更改应该对线程t2可见,对吗? Am I misunderstanding? 我误会了吗? So my question is why I am not getting the output: 所以我的问题是为什么我没有得到输出:

     x is: 2     Thread-0

You're using two separate instances of your Test class, so naturally the x in each of them only gets incremented once. 您正在使用Test类的两个独立实例,因此每个实例中的x只会增加一次。 It's effectively the same as this: 它实际上与此相同:

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

Since each instance of Test has its own x , you'll see 1 twice with that code too. 由于每个实例Test都有自己的x ,你会看到1与代码两次了。

To test synchronized access to the same instance, you need to use a single instance instead. 要测试对同一实例的同步访问,您需要使用单个实例。 For example: 例如:

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");
    }
}

Which will output 哪个会输出

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

or similar (naturally it varies depending on which thread gets scheduled when). 或类似的(当然,它取决于在什么时候安排的线程)。

Sometimes, it'll even look like this: 有时,它甚至看起来像这样:

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

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 : 这是因为在System.out.println语句中对x的访问是 synchronized之外 ,所以有时(并非总是) x将在synchronized块结束之后和println之前递增:

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

In more detail: 更详细:

  1. t1 enters the synchronized block t1进入同步块
  2. t2 tries to enter the synchronized block but has to wait because t1 has the lock t2尝试进入同步块但必须等待,因为t1具有锁定
  3. t1 increments x , making it 1 t1递增x ,使其为1
  4. t1 exits the synchronized block t1退出同步块
  5. t2 jumps in and increments x , making it 2 t2跳入并递增x ,使其为2
  6. t2 exits the synchronized block t2退出同步块
  7. t1 outputs the current value of x (2) t1输出x (2)的当前
  8. t2 outputs the current value of x (2) t2输出x (2)的当前

Note that Step 2 and Step 3 could be in any order, and Steps 6-8 could also be in any order. 请注意,步骤2和步骤3可以按任何顺序排列,步骤6-8也可以按任何顺序排列。

To reliably report x as it was within the synchronized block after being incremented, we'd want to either: 为了在递增后可靠地报告在同步块内的x ,我们想要:

  1. Move the println into the synchronized block println移动到synchronized块中

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

    or 要么

  2. Save the result of the increment in a local variable 将增量的结果保存在局部变量中

     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()); } 

Unless you have a really, really good reason to hold a synchronization lock during output, go with #2. 除非你有一个非常好的理由在输出期间保持同步锁定,否则请使用#2。


Side note: As I mentioned in a comment, ++x already writes its value back to x , that's what the increment operators do. 旁注:正如我在评论中提到的, ++x 已经将其值写回x ,这就是增量运算符的作用。 So the x = part of 所以x =部分

x = ++x;

...is unnecessary. ......是不必要的。 Either use the increment operator: 使用增量运算符:

++x;

...or don't: ......或者不:

x += 1;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM