嵌套同步块的缺点

4

学习线程和并发。考虑以下代码:

class A {

  protected final Object lock = new Object();

  public void remove(Object obj){
    synchronized(lock){
      // remove the object
    }
  }

  public void add(Object obj){
    synchronized(lock){
      // add the object
    }
  }
}

这段代码是线程安全的,因为在一个线程正在添加或删除时,没有两个不同的线程可以同时进行addremove操作。

现在考虑A的以下子类:

class B extends A {

  public void update1(Object original, Object newObj){
    remove(original);
    add(original); 
  }

  public void update2(Object original, Object newObj){
    synchronized(lock) {
      remove(original);
      add(newObj);
    }
  }

}

B 必须实现一个线程安全的 update 方法。据我所知,update1 不是线程安全的,因为该操作不是原子的,即在执行 removeadd 之间没有同步(如果我错了请纠正)。

update2 是否是实现线程安全的 update 方法的正确方式?是否存在在同一 lock 上嵌套同步块的缺点?


我认为这种代码存在死锁的危险。如果你使用JVM的更现代化的附加功能,如并发包、ExecutorService和parallelStream(如果你使用JDK 8),那么效果会更好。 - duffymo
2
@ortis 我认为他们希望删除和添加在事务中完成。在 update1 中,另一个线程可能会在调用之间进行删除或添加。 - Sotirios Delimanolis
1
@duffymo 在这两种情况下都使用了相同的锁对象,因此这不应该导致死锁。 - Marco13
3
@duffymo,使用ExecutorService替代什么?这些示例没有展示任何线程被创建,它们只展示可能在线程中调用的方法。ExecutorService仅仅是一个更高级别的抽象概念,用于让您的代码在多个线程中运行。 - Solomon Slow
ExecutorService运行什么?(提示:Runnable)。您应该编写不带同步的Runnable。 - duffymo
显示剩余2条评论
3个回答

10

update2是实现线程安全更新方法的正确方式吗?

是的,它是。您已经实现了原子性,并且与单个addremove方法的调用者兼容。

在相同锁上使用嵌套的同步块有什么缺点吗?

没有,因为锁是可重入的,这意味着第二次获取不会做任何额外的工作,只是记住锁被再次获取了一次,因此除非执行两个release操作,否则不会释放锁。


0

锁是可重入的,除非你到处都看到死锁。

可重入锁是指可以被同一线程重新获取的锁。

如果有一种情况必须要原子地删除和添加,为什么不在超类方法中提供removeAndAdd呢?

为了安全起见,请确保将您的锁对象标记为私有和最终的。


-1

使用 synchronized 会使整个对象“锁定”:当另一个线程运行对象同步方法时,没有其他线程可以调用任何方法。

这种行为可能会真正减慢某些程序。

作为替代方案,您可以使用 ReentrantLock

以下是如何将其应用于帖子中的代码:

class A {
  protected final Lock lock = new ReentrantLock();

  public void remove(Object obj){
    lock.lock();
    try {
      // remove the object
    } finally {
       lock.unlock();
    }
  }

  public void add(Object obj){
    lock.lock();
    try {
      // add the object
    } finally {
       lock.unlock();
    }
  }
}

class B extends A {
  // ...

  public void update2(Object original, Object newObj){
     lock.lock();
     try {
        remove(original);
        add(newObj);
     } finally {
       lock.unlock();
     }
  }
}

1
调用synchronized方法并不会“锁定”一个对象。它只是防止其他线程同时在同一个对象上进行同步。您的示例使用不同的手段来实现与@fernandohur的示例 完全 相同的结果。 - Solomon Slow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接