每当在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


当前回答

锁可以用于可见性,也可以用于保护一些数据不受可能导致竞争的并发修改的影响。

当您需要将基本类型操作设置为原子类型时,可以使用AtomicInteger之类的选项。

但是假设你有两个整数,它们像x和y坐标一样彼此相关,它们彼此相关,应该以原子的方式改变。然后使用相同的锁来保护它们。

锁应该只保护彼此相关的状态。不多不少。如果在每个方法中都使用synchronized(this),那么即使类的状态是不相关的,即使更新不相关的状态,所有线程也将面临争用。

class Point{
   private int x;
   private int y;

   public Point(int x, int y){
       this.x = x;
       this.y = y;
   }

   //mutating methods should be guarded by same lock
   public synchronized void changeCoordinates(int x, int y){
       this.x = x;
       this.y = y;
   }
}

在上面的例子中,我只有一个方法同时改变x和y,而不是两个不同的方法,因为x和y是相关的,如果我给了两个不同的方法分别改变x和y,那么它就不会是线程安全的。

这个例子只是为了演示它的实现方式,而不一定是这样。最好的方法是让它成为IMMUTABLE。

现在,与Point例子相反的是,@Andreas已经提供了一个TwoCounters的例子,其中状态被两个不同的锁保护,因为状态彼此不相关。

使用不同的锁来保护不相关的状态的过程称为锁剥离或锁分裂

其他回答

I think there is a good explanation on why each of these are vital techniques under your belt in a book called Java Concurrency In Practice by Brian Goetz. He makes one point very clear - you must use the same lock "EVERYWHERE" to protect the state of your object. Synchronised method and synchronising on an object often go hand in hand. E.g. Vector synchronises all its methods. If you have a handle to a vector object and are going to do "put if absent" then merely Vector synchronising its own individual methods isn't going to protect you from corruption of state. You need to synchronise using synchronised (vectorHandle). This will result in the SAME lock being acquired by every thread which has a handle to the vector and will protect overall state of the vector. This is called client side locking. We do know as a matter of fact vector does synchronised (this) / synchronises all its methods and hence synchronising on the object vectorHandle will result in proper synchronisation of vector objects state. Its foolish to believe that you are thread safe just because you are using a thread safe collection. This is precisely the reason ConcurrentHashMap explicitly introduced putIfAbsent method - to make such operations atomic.

总之

Synchronising at method level allows client side locking. If you have a private lock object - it makes client side locking impossible. This is fine if you know that your class doesn't have "put if absent" type of functionality. If you are designing a library - then synchronising on this or synchronising the method is often wiser. Because you are rarely in a position to decide how your class is going to be used. Had Vector used a private lock object - it would have been impossible to get "put if absent" right. The client code will never gain a handle to the private lock thus breaking the fundamental rule of using the EXACT SAME LOCK to protect its state. Synchronising on this or synchronised methods do have a problem as others have pointed out - someone could get a lock and never release it. All other threads would keep waiting for the lock to be released. So know what you are doing and adopt the one that's correct. Someone argued that having a private lock object gives you better granularity - e.g. if two operations are unrelated - they could be guarded by different locks resulting in better throughput. But this i think is design smell and not code smell - if two operations are completely unrelated why are they part of the SAME class? Why should a class club unrelated functionalities at all? May be a utility class? Hmmmm - some util providing string manipulation and calendar date formatting through the same instance?? ... doesn't make any sense to me at least!!

在c#和Java阵营中似乎有不同的共识。我看到的大多数Java代码使用:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

而大多数c#代码选择了更安全的:

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

c#语言当然更安全。如前所述,不能从实例外部对锁进行恶意/意外访问。Java代码也有这种风险,但随着时间的推移,Java社区似乎倾向于稍微不那么安全,但稍微更简洁的版本。

这并不是对Java的挖苦,只是我在这两种语言上工作的经验的反映。

首先需要指出的是:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

语义上等价于:

public synchronized void blah() {
  // do stuff
}

这是不使用synchronized(this)的一个原因。您可能会说,您可以围绕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.

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

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

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

一个例子:

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(…)时,会出现死锁,因为两个线程都不能获得其他对象的锁。

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