作者:Thomas Kotzmann and Christian Wimmer
译者: 辰浩云
java编程语言的一个主要优势就是它内置了多线程程序支持。可以为一个多线程共享的java对象加上锁,这样就可以实现同步访问, java使用原语来指定关键代码区域,作用于共享对象并且同一时间只能被一个线程执行。在第一个线程进入锁定共享对象的区域后, 若第二个线程也准备进入该区域,它必须等待第一个线程再一次释放锁后进入。
在java HotSpot虚拟机中,每个java对象的前面都有一个class指针和一个对象头标记字,在对象头标记字里,存储着标识哈希码以及 用于分代垃圾收集的年龄和锁状态标记位,还用来实现一个轻量级的同步锁方案。下面展示了该标记字的布局和不同锁状态的表示。
图的右侧说明了标准的锁定过程。如果一个对象被释放锁,标记字最后两位的值就是01,当方法在对象上同步时,对象头标记字和class指针 被存储在当前线程执行栈帧的锁记录里。然后虚拟机尝试通过CAS操作,在标记字里存入指向锁记录的指针。操作成功后,当前线程就 获取了锁。因为锁记录指针总是在标记字边界处对齐,标记字的最后两位就是00,用来标识对象已被锁定。
如果CAS操作失败(因为共享对象已经被前面的线程锁定),虚拟机首先判断共享对象的标记字是否指向当前线程的方法堆栈。 如果是,就表明这个线程已经拥有共享对象的锁,可以继续安全执行。如果不是,就再次尝试执行CAS。对于一个递归锁定的对象, 锁定记录是用0进行赋值而不是用对象标记字。只有当两个不同的线程同时在一个共享对象上同步时,这个轻量级锁才会膨胀为一个用于 管理阻塞线程的重量级监视器锁。
轻量级锁要比重量级锁开销小得多,不过他们的性能都会受到这样一个事实的影响:每个CAS操作必须在多处理器计算机上原子执行。 尽管大多数共享对象都被一个线程锁定和释放。在JDK1.6,这个缺陷是通过所谓的无存储(Store-free)偏向锁定技术 Russel06 实现的,使用方法类似于 Kawachiya02 。 只在第一个线程获取锁并执行原子的CAS操作成功后,将持有锁的线程ID放入标记字。随后该对象就偏向于这个线程。之后访问的线程如果 还是同一个线程,就不需要执行任何的原子操作或者CAS更新标记字。甚至也不需要初始化线程堆栈上的锁记录,因为偏向对象永远不会检查它。
当一个线程在一个已偏向其他线程的共享对象上同步时,必须通过使对象看起来像是按照正常锁定的方式来撤销偏向。遍历偏向线程的堆栈 ,根据轻量级锁定方案调整在堆栈中与共享对象关联的锁记录,从其中找出最旧的锁记录地址,作为指向锁记录的指针放入共享对象的标记字里。 在该操作执行时,所有的线程必须挂起。当获取共享对象的标识哈希码(System.identityHashCode)时,会将哈希码写入标记字里, 因为存放哈希码的位置与存放线程ID的位置相同,因此也会撤销该偏向。
如果程序已经明确设计为在多个线程中共享对象,例如生产者/消费者队列,这种程序设计就不适合使用偏向锁。因此,如果一个类的实例对象在过去 经常出现撤销偏向,就会禁用该类的偏向锁定,这称为批量撤销。如果在禁用了偏向锁定的类的实例对象上调用同步代码,它会执行标准 的轻量级锁定。使用该类创建的新的实例对象被标记为不可偏向。
一种被称为批量重偏向的类似机制,可以用来优化这种情况:一个类被多个线程锁定和释放,但从不发生抢占。 它不需要禁用类的偏向锁定,就能使类的所有实例对象的偏向锁定失效。在类中有一个作为时代的epoch值,用来标识偏向锁定是否有效。 这个值在对象分配的时候会复制到标记字里。然后在对应的类里,可以有效地将批量重偏向作为epoch的增量来实现 (译者注:每发生一次批量重偏向,epoch++)。在下一次这个类的实例对象被锁定时,程序会检测对象标记字里的epoch值和类中的epoch值是否相同, 如果不同,说明该类进行了批量重偏向,该类的偏向锁定失效。如果相同,就将共享对象重新偏向当前线程。
Synchronization涉及到虚拟机的多个部分:根据类里的oopDesc,markOopDesc定义对象头结构,轻量级锁的代码要集成在解释器和编译器中, ObjectMonitor类用来实现重量级锁,偏向锁定集中在类BiasedLocking中。可以通过虚拟机启动参数 -XX:+UseBiasedLocking开启,使用 -XX:-UseBiasedLocking关闭,在JDK1.6和JDK1.7默认开启,但只有在程序启动几秒后才会激活。因此要注意短运行的微检测程序 micro-benchmarks .如果有必要的话,可以使用VM参数 -XX:BiasedLockingStartupDelay=0 修改延迟激活时间。
[Agesen99] O. Agesen, D. Detlefs, A. Garthwaite, R. Knippel, Y. S. Ramakrishna, D. White: An Efficient Meta-lock for Implementing Ubiquitous Synchronization. In Proceedings of the ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, pages 207-222. ACM Press, 1999. doi:10.1145/320384.320402
[Bacon98] D. F. Bacon, R. Konuru, C. Murthy, M. Serrano: Thin Locks: Featherweight Synchronization for Java. In Proceedings of the ACM SIGPLAN Conference on Programming Language Design and Implementation, pages 258-268. ACM Press, 1998. doi:10.1145/277650.277734
[Kawachiya02] K. Kawachiya, A. Koseki, T. Onodera: Lock Reservation: Java Locks can Mostly do without Atomic Operations. In Proceedings of the ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, pages 130-141. ACM Press, 2002. doi:10.1145/582419.582433
[Russel06] K. Russell, D. Detlefs: Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing. In Proceedings of the ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, pages 263-272. ACM Press, 2006. doi:10.1145/1167473.1167496