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

7 java multithreading

我们假设我有两个线程t1,t2它们正在尝试访问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();

}
Run Code Online (Sandbox Code Playgroud)

这是我的输出:

    x is: 1     Thread-1
    x is: 1     Thread-0
Run Code Online (Sandbox Code Playgroud)

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

    x is: 1     Thread-1
    x is: 2     Thread-0
Run Code Online (Sandbox Code Playgroud)

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

     x is: 2     Thread-0
Run Code Online (Sandbox Code Playgroud)

T.J*_*der 9

你正在使用你的类的两个单独的实例Test,所以x它们中的每一个实际上只增加一次.它实际上与此相同:

Test test1 = new Test();
Test test2 = new Test();
test1.incX();
test2.incX();
Run Code Online (Sandbox Code Playgroud)

由于每个实例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");
    }
}
Run Code Online (Sandbox Code Playgroud)

哪个会输出

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

或类似的(当然,它取决于在什么时候安排的线程).

有时,它甚至看起来像这样:

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

这是因为访问xSystem.out.println声明之外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());
Run Code Online (Sandbox Code Playgroud)

更详细:

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

请注意,步骤2和步骤3可以是任何顺序,步骤6-8也可以是任何顺序.

为了x在递增后可靠地报告在同步块内,我们想要:

  1. 移动println到同步块

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

    要么

  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());
    }
    
    Run Code Online (Sandbox Code Playgroud)

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


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

x = ++x;
Run Code Online (Sandbox Code Playgroud)

......是不必要的.使用增量运算符:

++x;
Run Code Online (Sandbox Code Playgroud)

......或者不:

x += 1;
Run Code Online (Sandbox Code Playgroud)

  • @Hemlata:这是一个不同的问题.当/如果你问它,准确显示你正在运行的代码,你得到的输出等等.请记住,在Java内存模型中,一个线程完全有可能有一个值的陈旧副本.例如:线程1将`x`更新为`1`.线程1*读取*`x`将其传递给`println`.线程2将`x`更新为`2`,读取其值,将其传递给`println`,并且`println`写入它.然后线程1写入它之前读取的值.(它甚至可以得到陈旧的阅读.)从根本上说:printlns没有同步,因此它们的行为不像它们被同步. (3认同)