为什么同步块比同步方法更好?
为什么同步块比同步方法更好?
我已经开始学习线程中的同步。
同步方法:
public class Counter { private static int count = 0; public static synchronized int getCount() { return count; } public synchronized setCount(int count) { this.count = count; } }
同步块:
public class Singleton { private static volatile Singleton _instance; public static Singleton getInstance() { if (_instance == null) { synchronized(Singleton.class) { if (_instance == null) _instance = new Singleton(); } } return _instance; } }
何时应该使用同步方法和同步块?
为什么同步块比同步方法更好?
为什么同步代码块比同步方法更好?
同步方法和同步代码块的区别在于锁定的对象不同:
- 同步方法锁定整个对象。这意味着当一个线程正在运行该方法时,其他线程无法使用该对象中的任何同步方法。
- 同步代码块在synchronized关键字后的括号中锁定对象。这意味着在同步代码块退出之前,其他线程无法锁定该对象。
因此,如果您想锁定整个对象,请使用同步方法。如果您希望保持对象的其他部分对其他线程可访问,请使用同步代码块。
如果您谨慎选择锁定的对象,同步代码块会导致更少的竞争,因为整个对象/类不会被阻塞。
这同样适用于静态方法:同步静态方法将锁定整个类对象,而静态方法内的同步代码块将锁定括号中的对象。
所以,如果我有一个同步代码块,并且一个线程正在其中运行,另一个线程可以进入对象并在对象的其他地方运行一个同步代码块吗?如果我有一个同步方法,并且一个线程正在其中执行,其他线程是否可以在该对象中执行或者只能在同步区域中执行?
我认为这个回答可能会误导新手,因为它谈到了如何锁定“整个对象”。新手常常会认为当一个对象被“锁定”时,将阻止其他线程使用它。但事实并非如此。锁定对象只防止其他线程同时锁定相同的对象。如果线程A锁定某个对象,线程B仍然可以使用它并修改它,只要线程B不试图锁定它。
另外,如果一个同步方法锁定“整个对象”,那么同步代码块是否只锁定对象的一部分?我不认为这是你的本意,但对于新手来说很容易误解你的意思。我认为你想要比较使用一个或多个私有锁对象进行“精细粒度”锁定的类与使用自己公开可见实例进行“粗粒度”锁定的类的优点。
如果我们使用同步代码块,并且一个线程在其中运行,那么每个对象每次只能有一个线程执行代码块内的代码,并且在一个线程运行时,同一个对象的其他线程无法执行任何同步代码块(它们可以进入方法但无法执行代码块)或方法,因为当前对象已被锁定。同样,在同步方法的情况下,当一个线程在其中运行时,同一个对象的其他线程无法执行任何其他同步代码块或方法。对象锁定意味着1个线程/对象。
为什么同步块优于同步方法?
同步方法和同步块的主要区别在于同步的对象不同。当你同步一个方法时,实际上是将对象本身进行同步。对于静态方法而言,你同步的是对象所属的类。因此,下面的两段代码执行方式相同:
public synchronized int getCount() { // ... }
这段代码与下面的代码等价:
public int getCount() { synchronized (this) { // ... } }
如果你想控制特定对象的同步,或者只想将方法的一部分代码与对象进行同步,那么可以使用同步块。如果你在方法声明中使用` synchronized` 关键字,它将会将整个方法与对象或类进行同步。
对于类变量和方法也是类似的,只不过获取的是相应的`Class`对象的监视器,而不是实例的监视器。
是的,它们是不同的,但是对于为什么你永远不想在`this`上同步,有很好的理由,所以这个答案在我看来有点偏离了问题的核心。
我强烈反对你永远不愿意在`this`上同步。实际上,在连续调用一系列同步方法时,同步在一段时间内对对象进行同步是有益的。你不需要为每个方法调用释放和重新获取锁。同步有很多不同的模式,每种模式都有其优缺点,具体取决于情况。
同步方法和唯一顶级块为` synchronized(someObj)`的方法之间没有任何区别,实际上编译器为同步生成的代码就是`someObj==this`。因此,这样做没有任何优势,但却将内部细节暴露给外界,这显然破坏了封装性。嗯,有一个优点:你能够节省大约20个字节。
类的线程行为并不是一个内部细节,它是你代码的一部分。虽然经常被省略,但是我是否能在并发环境中使用一个类可能是它使用的一个重要方面。`synchronized`关键字确实泄漏了你如何管理线程。但我认为这并不是坏事,因为你可以更改线程控制并删除关键字,而不会破坏任何客户端代码。
你确实可以删除它(严格来说这也是错误的),但是你不能添加它而不会造成潜在的破坏。类是否需要进行内部锁定与其提供的功能无关。是的,一个类是否可以被并发使用是代码契约的一部分,但它是如何实现的是一个实现细节。
为什么使用同步块比使用同步方法更好?
尽管通常不是一个问题,但从安全的角度来看,最好是在私有对象上使用同步块,而不是将其放在方法上。
将其放在方法上意味着您正在使用对象本身的锁来提供线程安全。通过这种机制,恶意用户可以获取您对象的锁,并永远持有它,从而有效地阻塞其他线程。即使是一个善意的用户也可能无意中做同样的事情。
如果使用私有数据成员的锁,您可以防止这种情况发生,因为恶意用户不可能获取您私有对象上的锁。
这种技术在Bloch的《Effective Java》(第2版)的第70条中提到。
甚至不需要是恶意的,使用从某个地方获取的对象来进行锁定非常容易和无意的(比如因为您想要同步访问该对象)。最好避免这些问题。
:您能解释一下“恶意用户”的含义吗?他们如何通过搞乱自己的应用程序来实施恶意行为?
想象一下,您正在提供一种具有公共API的服务。如果您的API公开了一些可变对象,并且其操作依赖于锁定该对象,那么一个恶意的客户端可以获取该对象并锁定它,然后无限循环持有锁。这可能会阻止其他客户端能够正确操作。这基本上是一种拒绝服务类型的攻击。所以他们不仅仅是搞乱了他们自己的应用程序。
虽然这是一个旧的答案,但我对这个答案有一点评论。“恶意用户无法获取您的私有对象上的锁。”--如果“恶意用户”在同步块中使用的是一个不同的对象而不是“您的私有对象”,那该怎么办?
这种技术在《Effective Java》(第3版)的第82条中提到。
在您的示例中,服务必须与恶意客户端在同一个进程中。这是什么样的安全架构?例如,如果您的应用程序具有插件系统,那么这些插件可能应该在单独的进程中运行。
虽然您现在的评论是“旧的”,但我有一个回复。两个部分:(A)恶意用户无法控制您的代码选择锁定的对象,除非您提供某些明确的手段让他们做到这一点,(B)如果一个线程在某个对象O上进行同步,那对于在某个不同对象P上进行同步的任何其他线程都没有影响。