为什么我们需要为同步语句指定锁?

Why do we need to specify the lock for synchronized statements?

鉴于 class 的每个实例只有一个锁,那么为什么 Java 不允许我们这样做:

void method() {
    synchronized {
        // do something
    }

    // do other things
}

而不是这个:

void method() {
    synchronized (lock) {
        // do something
    }

    // do other things
}

指定锁的目的是什么?如果我选择一个对象作为另一个对象的锁,会有什么不同吗?或者我可以随便选择一个对象吗?

编辑:

原来我对同步方法的理解从根本上是错误的。

我认为不同的同步方法或块是完全独立的,与锁无关。相反,所有具有相同锁的同步方法或块只能由一个线程访问,即使此类同步 methods/blocks 来自不同的 classes(文档应该更强调这一点:ALL synced methods/blocks,无论位置如何,重要的是锁定)。

synchronized (lock)..中,lock可以是对象级锁,也可以是class级锁。

  • 示例 1 Class 级别锁定:

    private static Object lock=new Object();
    synchronized (lock){
    //do Something
    }
    
  • 示例 2 对象级锁定:

    private Object lock=new Object();
    synchronized (lock){
    //do Something
    }
    

这样您就可以锁定与 this 完全不同的东西。

还记得 Vector 是怎样 "thread-safe?" 没那么简单;每个 call 是,但像这样的代码不是因为它可以在获取向量大小和获取元素之间进行更新:

for (int i = 0; i < vector.size(); ++i) System.out.println(vector.get(i));

由于 VectorCollections.synchronized* 与旧的 synchronized 关键字同步,您可以通过将其全部封闭在一个锁中来使上面的代码线程安全:

synchronized (vector) {
    for (int i = 0; i < vector.size(); ++i) System.out.println(vector.get(i));
}

这可能在非线程安全、非同步或使用 ReentrantLock 的方法中;锁定向量与锁定 this.

是分开的

Given that there's only one lock for each instance of a class, then why doesn't Java just allow us to do this:

void method() {
    synchronized {
        // do something
    }

    // do other things
}

虽然每个实例都提供了一个内部锁, 这不一定是要使用的 "obvious" 锁。

您可能是对的,他们可以提供 synchronized { ... } 作为 synchronized (this) { ... } 的 shorthand。 我不知道他们为什么没有,但我从来没有错过。 但是并发编程很棘手, 因此,使锁定对象成为一个明确的必需参数可能会使读者更清楚,这是一件好事,正如@ajb 在评论中指出的那样。 无论如何,我不认为语法是你的主要问题,所以让我们继续。

What's the purpose of specifying a lock?

嗯,锁可能是同步机制中最重要的东西。同步的关键点是只有一个线程可以持有同一个锁。持有不同锁的两个线程是不同步的。所以知道什么是保护同步的锁是至关重要的。

Does it make a difference if I choose one object as a lock over the other?

希望上一节讲清楚,是的,你要慎重选择对象。它必须是所有相关线程可见的对象, 它必须不为空,并且它必须是在同步期间不会被重新分配的东西。

Or could I just choose any random object?

当然不是。请参阅上一节。

要了解 Java 中的并发性,我推荐有关该主题的书 Java Concurrency in Practice by one of the authors of the API, or Oracle's tutorials

如果你有多个对象b1/b2需要更新并发

class A {
    private B b1, b2;
}

如果你只有一把锁说 class A 本身

synchronized (this) { ... }

那么假设有两个线程同时在更新b1和b2,他们会一个一个播放,因为synchronized(this)

但是如果你有两个锁 b1 和 b2

private Object lock1 = new Object, lock2 = new Object;

我提到的两个线程将同时播放,因为 synchronized (lock1) 不影响 synchronized (lock2)。有时意味着更好性能。

使用什么对象作为锁肯定会有所不同。如果你说

void method() {
    synchronized (x) {
        // do something
    }

    // do other things
}

现在,如果一个线程正在执行该块,而另一个线程试图进入该块,如果 x 两者都相同,则第二个线程将不得不等待。但是如果 x 不同,第二个线程可以同时执行该块。因此,例如,如果 method 是一个实例方法并且您说

void method() {
    synchronized (this) {
        // do something
    }    
    // do other things
}

现在两个线程 运行 使用同一个对象的方法不能同时执行块,但是两个线程仍然可以 运行 不同对象的方法而不会互相阻塞。当您想要防止同时访问该对象中的实例变量时,这就是您想要的,但您没有任何其他需要保护的东西。如果两个线程正在访问两个不同对象中的变量,这不是问题。

但是假设代码块正在访问一个公共资源,并且您想确保所有其他线程都被锁定,无法访问该资源。例如,您正在访问一个数据库,并且该块进行了一系列更新,并且您希望确保它们以原子方式完成,即在两次更新之间没有其他代码应该访问数据库。现在 synchronized (this) 还不够好,因为您可以对两个不同的对象使用方法 运行ning 但访问同一个数据库。在这种情况下,您需要对可能访问同一数据库的所有对象使用相同的锁。在这里,使数据库对象本身成为锁就可以了。现在没有两个线程可以使用 method 同时进入这个块,如果它们使用同一个数据库,即使对象不同。