【问题标题】:Why do we need to specify the lock for synchronized statements?为什么我们需要为同步语句指定锁?
【发布时间】:2016-12-23 06:47:51
【问题描述】:

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

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

    // do other things
}

而不是这个:

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

    // do other things
}

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

编辑:

事实证明,我对同步方法的理解在基础层面上是错误的。

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

【问题讨论】:

  • 如果我选择一个对象作为另一个对象的锁,会有所不同吗? 是的,当然会有所不同。如果目标是将 this 用作锁,则使用 synchronized(this)。这就是常规同步方法的作用:它们在此同步。
  • 锁自己的前门或邻居的前门有区别吗?当然重要。如果你锁错了门,人们仍然可以进入你的房子。
  • 它只是任何随机对象,但它很重要,因此您可以拥有 2 个单独的互斥,例如,允许 2 个线程访问 2 个不同的方法,但每个方法一次只能访问一个线程。因此,两者在技术上是同步的。

标签: java concurrency synchronized


【解决方案1】:

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

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

    // do other things
}

虽然每个实例都提供了一个内在锁, 这不一定是要使用的“明显”锁。

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

指定锁的目的是什么?

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

如果我选择一个对象作为另一个对象的锁,会有所不同吗?

我希望前面的部分说清楚了,是的,你必须仔细选择对象。它必须是所有相关线程都可见的对象, 它必须不为空,并且必须是在同步期间不会被重新分配的东西。

或者我可以随便选择一个对象吗?

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

要了解 Java 中的并发性,我推荐该 API 的一位作者的书 Java Concurrency in Practice,或有关该主题的 Oracle's tutorials

【讨论】:

  • 我可以接受他们不提供 synchronized 作为 synchronized(this) 的简写,因为 (1) synchronized(this) 不一定是最常见的用法,并且 (2) 有锁明确的对象使读者更清楚。
【解决方案2】:

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

还记得Vector 是如何“线程安全的”吗?没那么简单。每个 调用 都是,但这样的代码不是因为它可以在获取向量的大小和获取元素之间进行更新:

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 是分开的。

【讨论】:

    【解决方案3】:

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

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

    现在,如果一个线程正在执行该块,而另一个线程试图进入该块,如果x 对他们来说是相同的,那么第二个线程将不得不等待。但是如果x不同,第二个线程可以同时执行该块。所以,例如,如果method 是一个实例方法,而你说

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

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

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

    【讨论】:

      【解决方案4】:

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

      class A {
          private B b1, b2;
      }
      

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

      synchronized (this) { ... }
      

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

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

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

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

      【讨论】:

        【解决方案5】:

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

        • Example1 类级别锁定:

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

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

        【讨论】:

          猜你喜欢
          • 2018-08-26
          • 2016-01-14
          • 1970-01-01
          • 2015-08-13
          • 2021-02-05
          • 2011-02-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多