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


当前回答

让我先把结论说出来——对私有字段的锁定对于稍微复杂一点的多线程程序是不起作用的。这是因为多线程是一个全局问题。本地化同步是不可能的,除非你以一种非常防御的方式写(例如,复制所有传递给其他线程的内容)。


下面是详细的解释:

同步包括三个部分:原子性、可见性和有序性

同步块是非常粗糙的同步级别。正如您所期望的那样,它加强了可见性和排序。但是对于原子性,它并不能提供太多的保护。原子性要求程序的全局知识,而不是局部知识。(这使得多线程编程非常困难)

假设我们有一个Account类,它有存取款方法。它们都是基于一个私有锁进行同步的,就像这样:

class Account {
    private Object lock = new Object();

    void withdraw(int amount) {
        synchronized(lock) {
            // ...
        }
    }

    void deposit(int amount) {
        synchronized(lock) {
            // ...
        }
    }
}

考虑到我们需要实现一个更高级别的类来处理传输,就像这样:

class AccountManager {
    void transfer(Account fromAcc, Account toAcc, int amount) {
        if (fromAcc.getBalance() > amount) {
            fromAcc.setBalance(fromAcc.getBalance() - amount);
            toAcc.setBalance(toAcc.getBalance + amount);
        }
    }
}

假设我们现在有两个账户,

Account john;
Account marry;

如果Account.deposit()和Account.withdraw()被内部锁定。这将导致问题时,我们有2个线程工作:

// Some thread
void threadA() {
    john.withdraw(500);
}

// Another thread
void threadB() {
    accountManager.transfer(john, marry, 100);
}

因为线程a和线程b有可能同时运行。线程B完成条件检查,线程A退出,线程B再次退出。这意味着即使约翰的账户上没有足够的钱,我们也可以从他那里提取100美元。这将打破原子性。

您可能会提出:为什么不将withdraw()和deposit()添加到AccountManager中呢?但是根据这个提议,我们需要创建一个多线程安全的Map,将不同的帐户映射到它们的锁。我们需要在执行后删除锁(否则会泄漏内存)。我们还需要确保没有其他用户直接访问Account.withdraw()。这将引入许多微妙的错误。

正确且最常用的方法是在Account中公开锁。并让AccountManager使用锁。但在这种情况下,为什么不直接使用对象本身呢?

class Account {
    synchronized void withdraw(int amount) {
        // ...
    }

    synchronized void deposit(int amount) {
        // ...
    }
}

class AccountManager {
    void transfer(Account fromAcc, Account toAcc, int amount) {
        // Ensure locking order to prevent deadlock
        Account firstLock = fromAcc.hashCode() < toAcc.hashCode() ? fromAcc : toAcc;
        Account secondLock = fromAcc.hashCode() < toAcc.hashCode() ? toAcc : fromAcc;

        synchronized(firstLock) {
            synchronized(secondLock) {
                if (fromAcc.getBalance() > amount) {
                    fromAcc.setBalance(fromAcc.getBalance() - amount);
                    toAcc.setBalance(toAcc.getBalance + amount);
                }
            }
        }
    }
}

简而言之,私有锁不适用于稍微复杂一点的多线程程序。

(转载自https://stackoverflow.com/a/67877650/474197)

其他回答

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

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

简单介绍一下。

线程和可共享实体 多个线程可以访问同一个实体,例如多个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---->的信息

如果你已经决定:

你要做的就是锁定目标 当前对象;而且 你想要 锁定粒度小于 整体方法;

那么我就不认为synchronizezd是一个禁忌。

Some people deliberately use synchronized(this) (instead of marking the method synchronized) inside the whole contents of a method because they think it's "clearer to the reader" which object is actually being synchronized on. So long as people are making an informed choice (e.g. understand that by doing so they're actually inserting extra bytecodes into the method and this could have a knock-on effect on potential optimisations), I don't particularly see a problem with this. You should always document the concurrent behaviour of your program, so I don't see the "'synchronized' publishes the behaviour" argument as being so compelling.

至于应该使用哪个对象的锁的问题,我认为在当前对象上同步并没有什么错,如果这是你所做的逻辑所期望的,以及你的类通常是如何被使用的。例如,对于集合,逻辑上期望锁定的对象通常是集合本身。

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

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

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

一个例子:

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

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

我将分别讨论每一点。

Some evil code may steal your lock (very popular this one, also has an "accidentally" variant) I'm more worried about accidentally. What it amounts to is that this use of this is part of your class' exposed interface, and should be documented. Sometimes the ability of other code to use your lock is desired. This is true of things like Collections.synchronizedMap (see the javadoc). All synchronized methods within the same class use the exact same lock, which reduces throughput This is overly simplistic thinking; just getting rid of synchronized(this) won't solve the problem. Proper synchronization for throughput will take more thought. You are (unnecessarily) exposing too much information This is a variant of #1. Use of synchronized(this) is part of your interface. If you don't want/need this exposed, don't do it.

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