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


当前回答

我只想提到一种可能的解决方案,用于在没有依赖关系的原子代码部分中惟一的私有引用。您可以使用带锁的静态Hashmap和名为atomic()的简单静态方法,该方法使用堆栈信息(完整的类名和行号)自动创建所需的引用。然后,您可以在同步语句中使用此方法,而无需写入新的锁对象。

// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
    StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point 
    StackTraceElement exepoint = stack[2];
    // creates unique key from class name and line number using execution point
    String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber()); 
    Object lock = locks.get(key); // use old or create new lock
    if (lock == null) {
        lock = new Object();
        locks.put(key, lock);
    }
    return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 1
        ...
    }
    // other command
}
// Synchronized code
void dosomething2() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 2
        ...
    }
    // other command
}

其他回答

在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的挖苦,只是我在这两种语言上工作的经验的反映。

虽然我同意不要盲目地遵守教条规则,但“偷锁”的场景对你来说是不是很古怪?一个线程确实可以从你的对象“外部”获得锁(synchronized(theObject){…}),阻塞其他线程等待同步实例方法。

如果您不相信恶意代码,请考虑这些代码可能来自第三方(例如,如果您开发了某种应用程序服务器)。

“意外”版本似乎不太可能,但就像他们说的那样,“让一些东西不受白痴的影响,就会有人发明一个更好的白痴”。

所以我同意“这取决于这个班级做什么”的观点。


编辑以下eljenso的前3条评论:

我从来没有遇到过偷锁的问题,但这里有一个想象的场景:

假设您的系统是一个servlet容器,我们考虑的对象是ServletContext实现。它的getAttribute方法必须是线程安全的,因为上下文属性是共享数据;所以你声明它是同步的。让我们再想象一下,您提供了一个基于容器实现的公共托管服务。

我是您的客户,并在您的站点上部署我的“好”servlet。我的代码碰巧包含对getAttribute的调用。

黑客伪装成另一个客户,在您的站点上部署恶意servlet。它在init方法中包含以下代码:

synchronized (this.getServletConfig().getServletContext()) {
   while (true) {}
}

假设我们共享相同的servlet上下文(只要两个servlet位于同一个虚拟主机上,规范就允许),那么我对getAttribute的调用将永远锁定。黑客已经在我的servlet上实现了DoS。

如果getAttribute在私有锁上同步,则这种攻击是不可能的,因为第三方代码无法获得此锁。

我承认这个例子是人为设计的,对servlet容器如何工作的看法过于简单,但恕我直言,它证明了这一点。

因此,我将基于安全性考虑做出设计选择:我是否能够完全控制访问实例的代码?线程无限期地持有实例锁的后果是什么?

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!!

这要视情况而定。 如果只有一个或多个共享实体。

在这里查看完整的工作示例

简单介绍一下。

线程和可共享实体 多个线程可以访问同一个实体,例如多个connectionThreads共享一个messageQueue。由于线程并发运行,可能会有一个数据被另一个覆盖的机会,这可能是一个混乱的情况。 因此,我们需要某种方法来确保可共享实体一次只能被一个线程访问。(并发)。

同步块 Synchronized()块是一种确保可共享实体并发访问的方法。 首先,打个小比方 假设有两个人P1, P2(线程)一个盥洗室(可共享实体),有一扇门(锁)。 现在我们想让一个人一次使用脸盆。 一种方法是在P1锁门的时候P2等待P1完成他的工作 P1打开门 那么只有p1可以使用脸盆。

语法。

synchronized(this)
{
  SHARED_ENTITY.....
}

"this" provided the intrinsic lock associated with the class (Java developer designed Object class in such a way that each object can work as monitor). Above approach works fine when there are only one shared entity and multiple threads (1: N). N shareable entities-M threads Now think of a situation when there is two washbasin inside a washroom and only one door. If we are using the previous approach, only p1 can use one washbasin at a time while p2 will wait outside. It is wastage of resource as no one is using B2 (washbasin). A wiser approach would be to create a smaller room inside washroom and provide them one door per washbasin. In this way, P1 can access B1 and P2 can access B2 and vice-versa.

washbasin1;  
washbasin2;

Object lock1=new Object();
Object lock2=new Object();

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }


在这里查看更多关于Threads---->的信息

一个使用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)可能是唯一的解决方案)。