每当在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
如果可能的话,让你的数据不可变(最终变量)
如果你不能避免跨多个线程共享数据的突变,使用高级编程结构[例如,粒度锁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包。
这里已经说过,同步块可以使用用户定义的变量作为锁对象,当同步函数只使用“this”时。当然,你也可以对函数中需要同步的部分进行操作。
但是每个人都说synchronized函数和block之间没有区别,block覆盖了使用“this”作为锁对象的整个函数。这是不对的,不同的是字节码,将在这两种情况下产生。在同步块使用的情况下,应该分配本地变量,其中包含引用“this”。因此,我们会得到一个更大的函数(如果你只有几个函数,这就无关紧要了)。
你可以在这里找到更详细的解释:
http://www.artima.com/insidejvm/ed2/threadsynchP.html
同步块的使用也不好,原因如下:
synchronized关键字在一个方面非常有限:当退出一个同步块时,所有等待该锁的线程都必须被解除阻塞,但只有其中一个线程可以获得锁;所有其他人都看到锁已被占用,并返回阻塞状态。这不仅仅是浪费了大量的处理周期:为解除线程阻塞而进行的上下文切换通常还涉及从磁盘调出内存,这是非常非常昂贵的。
关于这方面的更多细节,我建议你阅读这篇文章:
http://java.dzone.com/articles/synchronized-considered
锁可以用于可见性,也可以用于保护一些数据不受可能导致竞争的并发修改的影响。
当您需要将基本类型操作设置为原子类型时,可以使用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的例子,其中状态被两个不同的锁保护,因为状态彼此不相关。
使用不同的锁来保护不相关的状态的过程称为锁剥离或锁分裂