每当在SO上出现关于Java同步的问题时,有些人都急于指出应该避免同步(这一点)。相反,他们声称,对私有引用的锁定是首选的。

其中一些原因是:

一些邪恶的代码可能会偷你的锁(这个很流行,也有一个“意外”变体) 同一类中的所有同步方法都使用完全相同的锁,这降低了吞吐量 你(不必要地)暴露了太多信息

包括我在内的其他人认为,synchronized(this)是一个被大量使用的习语(在Java库中也是如此),是安全的,而且很容易理解。它不应该被避免,因为你有一个错误,你不知道你的多线程程序中发生了什么。换句话说:如果它是适用的,那么就使用它。

我感兴趣的是看到一些现实世界的例子(没有foobar的东西),避免锁定在这是可取的,当同步(这)也会做的工作。

因此:您应该总是避免synchronized(this),并将其替换为私有引用上的锁吗?


一些进一步的信息(随着答案的更新):

we are talking about instance synchronization both implicit (synchronized methods) and explicit form of synchronized(this) are considered if you quote Bloch or other authorities on the subject, don't leave out the parts you don't like (e.g. Effective Java, item on Thread Safety: Typically it is the lock on the instance itself, but there are exceptions.) if you need granularity in your locking other than synchronized(this) provides, then synchronized(this) is not applicable so that's not the issue


当前回答

一个使用synchronized(this)的好例子。

// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
   // some code here...
   Set ls;
   synchronized(this) {
      ls = listeners.clone();
   }
   for (IListener l : ls) { l.processEvent(event); }
   // some code here...
}

正如你在这里看到的,我们在这个上使用同步来方便地与那里的一些同步方法进行长周期(可能是无限循环的run方法)合作。

当然,在私有字段上使用synchronized可以很容易地重写。但有时,当我们已经有了一些同步方法的设计时(例如,我们从遗留类派生出来的,synchronized(this)可能是唯一的解决方案)。

其他回答

简单的回答:您必须了解其中的区别,并根据代码做出选择。

Long answer: In general I would rather try to avoid synchronize(this) to reduce contention but private locks add complexity you have to be aware of. So use the right synchronization for the right job. If you are not so experienced with multi-threaded programming I would rather stick to instance locking and read up on this topic. (That said: just using synchronize(this) does not automatically make your class fully thread-safe.) This is a not an easy topic but once you get used to it, the answer whether to use synchronize(this) or not comes naturally.

避免使用synchronized(this)作为锁定机制:这会锁定整个类实例,并可能导致死锁。在这种情况下,重构代码以只锁定特定的方法或变量,这样整个类就不会被锁定。同步可以在方法级别内使用。 下面的代码展示了如何锁定一个方法,而不是使用synchronized(this)。

   public void foo() {
if(operation = null) {
    synchronized(foo) { 
if (operation == null) {
 // enter your code that this method has to handle...
          }
        }
      }
    }

不进行同步的原因是,有时您需要多个锁(经过一些额外的思考后,第二个锁通常会被删除,但您仍然需要它处于中间状态)。如果你锁定了这个,你总是要记住两个锁中哪个是这个;如果你锁定一个私有对象,变量名会告诉你。

从读者的角度来看,如果你看到了锁定,你总是必须回答两个问题:

这能保护什么样的权限? 一把锁真的够了吗,难道不是有人引入了漏洞吗?

一个例子:

class BadObject {
    private Something mStuff;
    synchronized setStuff(Something stuff) {
        mStuff = stuff;
    }
    synchronized getStuff(Something stuff) {
        return mStuff;
    }
    private MyListener myListener = new MyListener() {
        public void onMyEvent(...) {
            setStuff(...);
        }
    }
    synchronized void longOperation(MyListener l) {
        ...
        l.onMyEvent(...);
        ...
    }
}

如果两个线程在BadObject的两个不同实例上开始longOperation(),它们将获得 他们的锁;当调用l.onMyEvent(…)时,会出现死锁,因为两个线程都不能获得其他对象的锁。

在本例中,我们可以通过使用两个锁来消除死锁,一个用于短操作,一个用于长操作。

如果可能的话,让你的数据不可变(最终变量) 如果你不能避免跨多个线程共享数据的突变,使用高级编程结构[例如,粒度锁API]

Lock提供对共享资源的独占访问:一次只有一个线程可以获得锁,并且对共享资源的所有访问都要求首先获得锁。

使用ReentrantLock实现Lock接口的示例代码

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

锁定相对于同步的优势

The use of synchronized methods or statements forces all lock acquisition and release to occur in a block-structured way. Lock implementations provide additional functionality over the use of synchronized methods and statements by providing A non-blocking attempt to acquire a lock (tryLock()) An attempt to acquire the lock that can be interrupted (lockInterruptibly()) An attempt to acquire the lock that can timeout (tryLock(long, TimeUnit)). A Lock class can also provide behavior and semantics that is quite different from that of the implicit monitor lock, such as guaranteed ordering non-re entrant usage Deadlock detection

看看这个关于各种锁的SE问题:

同步vs锁定

您可以通过使用高级并发API而不是synchronized块来实现线程安全。该文档页提供了实现线程安全的良好编程结构。

锁对象支持简化许多并发应用程序的锁定习惯用法。

executor为启动和管理线程定义了高级API。concurrent提供的执行器实现提供了适合大型应用程序的线程池管理。

并发集合使管理大型数据集合变得更容易,并且可以大大减少同步的需要。

原子变量具有最小化同步和帮助避免内存一致性错误的特性。

ThreadLocalRandom(在JDK 7中)提供了从多个线程有效生成伪随机数的功能。

其他编程结构也可以参考java.util.concurrent和java.util.concurrent.atomic包。

不,你不应该总是这样。但是,当一个特定对象上有多个关注点时,我倾向于避免它,而这些关注点只需要对它们本身是线程安全的。例如,你可能有一个可变数据对象,它有“label”和“parent”字段;它们需要是线程安全的,但是改变其中一个不需要阻止另一个被写入/读取。(在实践中,我将通过声明字段为volatile和/或使用java.util来避免这种情况。concurrent的AtomicFoo包装器)。

一般来说,同步有点笨拙,因为它只是一个大的锁定,而不是仔细考虑如何允许线程相互工作。使用synchronized(this)更加笨拙和反社会,因为它表示“当我持有锁时,没有人可以更改这个类的任何内容”。你需要多久做一次?

I would much rather have more granular locks; even if you do want to stop everything from changing (perhaps you're serialising the object), you can just acquire all of the locks to achieve the same thing, plus it's more explicit that way. When you use synchronized(this), it's not clear exactly why you're synchronizing, or what the side effects might be. If you use synchronized(labelMonitor), or even better labelLock.getWriteLock().lock(), it's clear what you are doing and what the effects of your critical section are limited to.