在非静态方法中使用同步块锁定实例

Locking instances with synchronized block inside non-static method

按照下面的代码,我有两个 class A 的实例 - a1 和 a2。并分别在两个实例上调用方法 foo()。

foo() 方法中有一个同步块,它锁定在调用对象上。由于它是实例级锁定,因此这两个方法应该同时开始执行,因为它们是从两个单独的实例中调用的。但是,它们是按顺序执行的。

是否因为两个实例都从同一个 Thread main 调用?

代码更改:制作 class A 实现 Runnable,将 foo() 重命名为 运行(),从主线程分叉线程 t,从主线程调用 a1.run(),从线程 t 调用 a2.run() 。虽然两个 A 实例 - a1 和 a2 正在从两个线程 - 主线程和线程 t 调用,但同步块(this)似乎被锁定了。

我的理解是 'this' 指的是不同的调用 Runnable 实例,甚至线程也不同。所以,Thread.sleep 不应该让另一个线程阻塞。那么,为什么运行的两次调用不是并行发生的呢?

Expected Output (should execute parallel)

main <time> Inside A.run
Thread-0 <time> Inside A.run
Thread-0 <time+4s> Exiting A.run
main <time+5s> Exiting A.run

Actual Output (executing sequentially)

main <time> Inside A.run
main <time+5s> Exiting A.run
Thread-0 <time+5s> Inside A.run
Thread-0 <time+9s> Exiting A.run 

import java.time.*;
import java.time.format.DateTimeFormatter;

public class Test {

    public static void main(String[] args) {
        /*A a1 = new A(5000); A a2 = new A(4000);
        a1.foo(); a2.foo();*/
        A a1 = new A(5000); A a2 = new A(4000);
        Thread t = new Thread(a2);
        /*a1.run(); t.start();*/
        t.start(); a1.run(); // <-- putting t.start() before a1.run() solves the issue
    }

}

class A implements Runnable {
    public long waitTime;
    public A() {}
    public A(long timeInMs) {
        waitTime = timeInMs;
    }
    public void run() {
        synchronized(this) {
            try {
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
                LocalDateTime time = LocalDateTime.now();
                System.out.println(Thread.currentThread().getName() + " " + formatter.format(time) + " Inside A.run");
                Thread.sleep(waitTime);
                time = LocalDateTime.now();
                System.out.println(Thread.currentThread().getName() + " " + formatter.format(time) + " Exiting A.run");
            } catch (InterruptedException e) {}
        }
    }

}

Is it because, both the instances getting invoked from the same Thread main?

是的。调用 Thread.sleep() 是同步的,除非被中断,否则将在持续时间内阻塞当前线程。您直接调用 a1.foo() ,这将在持续时间内阻塞主线程,这就是您看到的结果。创建单独的线程并在每个线程中调用 foo() ,您将看到您期望的行为。

你在启动线程之前就开始同步运行a1,当然你会在主线程上得到a1的输出,因为直到它才能到达线程启动语句a1完成了。

尝试在主线程上 运行a1 之前先启动线程 运行ning a2,看看你会得到什么。

您还应注意线程调度可能会延迟,并且调用 Thread#start 不会立即开始在单独的线程上执行,而是将其排队等待系统线程调度程序。您可能还想考虑使用 CyclicBarrier 之类的同步设备,以便在线程 运行ning a2 和主线程 运行ning [=10= 之间进行协调],否则您可能 仍然 得到完全相同的结果,即使看起来您在 a2 之前启动到 运行 a1 的线程。