作为一个越来越熟悉Java的C ++程序员,看到语言级别支持锁定任意对象而没有任何声明对象支持这种锁定,这有点奇怪。为每个对象创建互斥锁似乎是一个自动选择的高成本。除了内存使用,互斥体在某些平台上是OS有限的资源。如果互斥锁不可用但你的性能特征明显不同,你可以旋转锁定,我希望这会损害可预测性。
在所有情况下,JVM是否足够智能,以识别特定对象永远不会成为synchronized关键字的目标,从而避免创建互斥锁?可以懒惰地创建互斥锁,但是这会引发自举需要互斥锁的自举问题,即使解决了这个问题,我也认为仍然需要一些开销来跟踪是否已经创建了互斥锁。所以我假设如果这样的优化是可能的,它必须在编译时或启动时完成。在C ++中,由于编译模型这样的优化是不可能的(你不知道对象的锁是否会跨库边界使用),但我对Java的编译和链接知道不够了解如果适用同样的限制。
答案 0 :(得分:15)
作为一个看过某些JVM实现锁定的方式的人......
通常的做法是从对象的标题字中的几个保留位开始。如果对象永远不会被锁定,或者它被锁定但没有争用它会保持这种状态。如果在锁定对象上发生争用,JVM 会将锁定膨胀成一个完整的互斥锁数据结构,并且它会在对象的生命周期内保持这种状态。
编辑 - 我刚刚注意到OP正在谈论操作系统支持的互斥锁。在我看过的例子中,未充气的互斥体是使用CAS指令等直接实现的,而不是使用pthread库函数等。
答案 1 :(得分:2)
你永远不能确定一个对象永远不会被用作锁(考虑反射)。通常,每个对象都有一个标题,其中一些位专用于锁。可以实现它,以便只根据需要添加标头,但这有点复杂,你可能还需要一些标头(类(相当于“vtbl”和C ++中的分配大小),哈希代码和垃圾收集)
Here's a wiki page on the implementation of synchronisation in the OpenJDK.
(在我看来,为每个对象添加锁是一个错误。)
答案 2 :(得分:2)
这实际上是JVM的实现细节,不同的JVM可能以不同的方式实现它。但是,它肯定是 not 可以在编译时进行优化的东西,因为Java在运行时链接,这样以前未知的代码就可以获取在旧代码中创建的对象并启动同步它。
请注意,在Java术语中,同步原语称为“监视器”而不是互斥,并且它由特殊字节码操作支持。有一个相当详细的解释here。
答案 3 :(得分:1)
JVM不能直接使用比较和交换指令吗?假设每个对象都有一个字段lockingThreadId
,用于存储锁定它的线程的id,
while( compare_and_swap (obj.lockingThreadId, null, thisThreadId) != thisTheadId )
// failed, someone else got it
mark this thread as waiting on obj.
shelf this thead
//out of loop. now this thread locked the object
do the work
obj.lockingThreadId = null;
wake up threads waiting on the obj
这是一个玩具模型,但它似乎并不太昂贵,并且不依赖于操作系统。