同步在 Java 中的工作原理

2022-09-02 20:17:10

首先,下面是一个示例

public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s has bowed to me!%n", 
                    this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s has bowed back to me!%n",
                    this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
}

我不明白的是堵塞是如何发生的。main 函数启动两个线程,每个线程都开始自己的弓。

“同步”究竟阻止了什么?为同一对象运行的相同函数(正如我最初认为的那样)?同一类的所有对象使用相同的函数?同一对象的所有同步函数?同一类的所有对象的所有同步函数?

帮帮我吧。


答案 1

在 Java 中,每个线程都提供了在其上或锁定线程的功能。同步方法时,该方法使用其对象实例作为锁。在您的示例中,方法和都是 ,并且两者都位于同一类中。这意味着执行这些方法的任何线程都将在实例上作为其锁定进行同步。ObjectsynchronizebowbowBacksynchronizedFriendFriend

将导致死锁的一系列事件是:

  1. 第一个 Thread 开始调用 ,它位于对象上。这意味着线程必须从此对象获取锁。alphonse.bow(gaston)synchronizedalphonseFriend
  2. 第二个 Thread 开始调用 ,该调用位于对象上。这意味着线程必须从此对象获取锁。gaston.bow(alphonse)synchronizedgastonFriend
  3. 现在,启动的第一个线程将调用并等待释放锁定。bowbackgaston
  4. 第二个线程现在开始调用并等待释放锁定。bowbackalphonse

更详细地显示事件的顺序:

  1. main()开始在主Therad中执行(称为线程#1),创建两个实例。目前为止,一切都好。Friend
  2. 主线程使用代码启动其第一个新线程(称为线程 #2)。线程 #2 调用 对象上的 。因此,线程 #2 获取对象的“锁”并进入方法。new Thread(new Runnable() { ...alphonse.bow(gaston)synchronizedalphonseFriendalphonsebow
  3. 此处出现一个时间片,原始线程有机会进行更多处理。
  4. 主线程启动第二个新线程(称为线程 #3),就像第一个线程一样。线程 #3 调用 ,在对象上同步。由于还没有人获取对象实例的“锁”,因此线程 #3 成功获取此锁并进入方法。gaston.bow(alphonse)gastonFriendgastonbow
  5. 此处出现一个时间片,线程 #2 有机会执行更多处理。
  6. 线程 #2 现在调用时是对 的实例的引用。这在逻辑上等效于调用 。因此,此方法位于实例上。此对象的锁已被获取,并由另一个线程(线程 #3)持有。因此,线程 #2 必须等待释放锁。线程被置于等待状态,允许线程 #3 进一步执行。bower.bowBack(this);bowergastongaston.bowBack(alphonse)synchronizedgastongaston
  7. 线程 #3 现在调用 ,在此实例中,这在逻辑上与调用 相同。为此,它需要获取实例的锁,但此锁由线程 #2 持有。此线程现在处于等待状态。bowbackalphonse.bowBack(gaston)alphonse

您现在处于两个线程都无法执行的位置。线程 #2 和线程 #3 都在等待释放锁。但是,如果没有线程取得进展,这两个锁都无法释放。但是,如果没有释放锁,这两个线程都无法取得进展。

因此:死锁!

死锁通常依赖于发生的特定事件序列,这可能会使调试变得困难,因为它们可能难以重现。


答案 2

同步有两个效果

  • 首先,在同一对象上两次调用同步方法不可能交错。当一个线程为对象执行同步方法时,调用同一对象块的同步方法的所有其他线程(挂起执行),直到第一个线程完成该对象。
  • 其次,当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用建立“发生之前”关系。这保证了对对象状态的更改对所有线程都可见。

因此,简而言之,它阻止了对同一对象上同步方法的任何调用。