【问题标题】:Java Synchronization LockJava 同步锁
【发布时间】:2011-07-23 20:24:41
【问题描述】:

当我们说我们使用 synchronized 关键字锁定一个对象时,这是否意味着我们正在获取整个对象的锁定,还是只锁定该块中存在的代码?

在下面的例子中listOne.add是同步的,这是否意味着如果另一个线程访问listOne.get它会被阻塞,直到第一个线程退出这个块?如果第二个线程在第一个线程仍在同步块中时访问同一对象的实例变量上的listTwo.getlistTwo.add 方法怎么办?

List<String> listONe = new ArrayList<String>();
List<String> listTwo = new ArrayList<String>();

/* ... ... ... */

synchronized(this) {
    listOne.add(something);
}

【问题讨论】:

    标签: java concurrency synchronized


    【解决方案1】:

    Java语言规范definessynchronized语句的含义如下:

    synchronized 语句代表正在执行的线程获取互斥锁(第 17.1 节),执行一个块,然后释放锁。当执行线程拥有锁时,没有其他线程可以获取锁。

    SynchronizedStatement:`
        synchronized ( Expression ) Block`
    

    Expression的类型必须是引用类型,否则会出现编译时错误。

    通过首先评估表达式来执行同步语句。

    如果表达式的评估由于某种原因突然完成,那么同步语句也会因为同样的原因而突然完成。

    否则,如果 Expression 的值为 null,则抛出 NullPointerException。

    否则,令Expression的非空值为V。执行线程锁定与V关联的锁。然后执行Block。如果 Block 的执行正常完成,则锁被解锁并且同步语句正常完成。如果 Block 的执行由于任何原因突然完成,则锁被解锁,同步语句随后由于同样的原因突然完成。

    获取与对象关联的锁本身并不会阻止其他线程访问对象的字段或调用对象上的非同步方法。其他线程也可以通过传统方式使用同步方法或同步语句来实现互斥。

    也就是说,在你的例子中

    synchronized(this) {
        listOne.add(something);
    }
    

    同步块确实以任何特殊方式处理listOne 引用的对象,其他线程可以随意使用它。但是,它确保没有其他线程可以同时进入this 引用的对象的同步块。因此,如果所有使用listOne 的代码都在同一个对象的同步块中,那么在任何给定时间最多有一个线程可以使用listOne

    还要注意,被锁定的对象没有得到特殊的保护以防止并发访问其状态,所以代码

    void increment() {
        synchronized (this) {
            this.counter = this.counter + 1;
        }
    }
    
    void reset() {
        this.counter = 0;
    }
    

    同步不正确,因为第二个线程可能在第一个线程已读取但尚未写入 counter 时执行 reset,从而导致重置被覆盖。

    【讨论】:

      【解决方案2】:

      给定方法:

        public void a(String s) {
          synchronized(this) {
            listOne.add(s);
          }
        }
      
        public void b(String s) {
          synchronized(this) {
            listTwo.add(s);
          }
        }
      
        public void c(String s) {
            listOne.add(s);
        }
      
        public void d(String s) {
            synchronized(listOne) {
              listOne.add(s);
            }
        }
      

      您不能同时调用 a 和 b,因为它们被锁定在同一个锁上。 但是,您可以同时调用 a 和 c(显然有多个线程),因为它们没有锁定在同一个锁上。这可能会导致 listOne 出现问题。

      您也可以同时调用 a 和 d,因为在这种情况下 d 与 c 没有什么不同。它不使用相同的锁。

      重要的是,您始终使用相同的锁锁定 listOne,并且在没有锁的情况下不允许访问它。如果 listOne 和 listTwo 以某种方式相关并且有时需要同时/原子地更新,则您需要一个锁来访问它们。否则 2 个单独的锁可能会更好。

      当然,如果您只需要一个并发列表,您可能会使用相对较新的 java.util.concurrent 类:)

      【讨论】:

        【解决方案3】:

        锁定在您包含在同步块中的对象实例上。

        但要小心!该对象本质上被锁定以供其他线程访问。只有执行相同 synchronized(obj) 的线程,其中 obj 在您的示例中为 this,但在其他线程中也可能是变量引用,请等待该锁。

        因此,不执行任何同步语句的线程可以访问“锁定”对象的任何和所有变量,您可能会遇到竞争条件。

        【讨论】:

          【解决方案4】:

          您需要了解锁定是建议性的,而不是物理强制执行的。例如,如果您决定在哪里使用Object 来锁定对某些类字段的访问,那么您必须以这样一种方式编写代码,以便在访问这些字段之前实际获取锁。如果不这样做,您仍然可以访问它们,并可能导致死锁或其他线程问题。

          例外情况是在方法上使用 synchronized 关键字,运行时将自动为您获取锁,而无需您执行任何特殊操作。

          【讨论】:

            【解决方案5】:
            synchronized(this) {
            

            只会锁定对象this。锁定和使用对象listOne

            synchronized(listOne){
                listOne.add(something);
            }
            

            以便多个线程一次访问一个 listOne。

            见:http://download.oracle.com/javase/tutorial/essential/concurrency/locksync.html

            【讨论】:

              【解决方案6】:

              仅当您在同一实例上具有同步块时,其他线程才会阻塞。所以列表本身的任何操作都不会阻塞。

              【讨论】:

                猜你喜欢
                • 2013-04-25
                • 2021-01-18
                • 1970-01-01
                • 1970-01-01
                • 2011-04-26
                • 2013-01-16
                • 1970-01-01
                • 2019-06-01
                • 1970-01-01
                相关资源
                最近更新 更多