我的原则:先会用再说,内部慢慢来
文章目录
- 一、线程八锁
- 二、场景分析
- 1. 两个普通同步方法,两个线程,标准打印, 打印? //one two
- 2. 新增 Thread.sleep() 给 getOne() ,打印? //one two
- 3. 新增普通方法 getThree() , 打印? //three one two
- 4. 两个普通同步方法,两个 Number 对象,打印? //two one
- 5. 修改 getOne() 为静态同步方法,打印? //two one (重点)
- 6. 修改两个方法均为静态同步方法,一个 Number 对象? //one two
- 7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象? //two one 对比5 (重点)
- 8. 两个静态同步方法,两个 Number 对象? //one two 对比6
- 9. === 场景结论 ===
- 三、代码反编译 javap
- 四、Java对象头与Monitor
- 五、synchronized 底层分析
- 六、锁优化
- 七、番外篇
一、线程八锁
题目:判断打印的 “one” or “two” ?
- 两个普通同步方法,两个线程,标准打印, 打印? //one two
- 新增 Thread.sleep() 给 getOne() ,打印? //one two
- 新增普通方法 getThree() , 打印? //three one two
- 两个普通同步方法,两个 Number 对象,打印? //two one
- 修改 getOne() 为静态同步方法,打印? //two one (重点)
- 修改两个方法均为静态同步方法,一个 Number 对象? //one two
- 一个静态同步方法,一个非静态同步方法,两个 Number 对象? //two one (重点) 对比5
- 两个静态同步方法,两个 Number 对象? //one two 对比6
二、场景分析
1. 两个普通同步方法,两个线程,标准打印, 打印? //one two
代码:
public class _15_01_TestSynchronized {
public static void main(String[] args) throws Exception{
Number01 number = new Number01();
new Thread(() -> number.getOne(),"A").start();
Thread.sleep(50);
new Thread(() -> number.getTwo(),"B").start();
}
}
class Number01{
public synchronized void getOne(){
System.out.println(Thread.currentThread().getName() + "_one");
}
public synchronized void getTwo(){//this
System.out.println(Thread.currentThread().getName() + "_two");
}
}
输出:
A_one
B_two
2. 新增 Thread.sleep() 给 getOne() ,打印? //one two
代码:
public class _15_02_TestSynchronized {
public static void main(String[] args) throws Exception{
Number02 number = new Number02();
new Thread(() -> number.getOne(),"A").start();
Thread.sleep(50);
new Thread(() -> number.getTwo(),"B").start();
}
}
class Number02{
public synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "_one");
}
public synchronized void getTwo(){//this
System.out.println(Thread.currentThread().getName() + "_two");
}
}
输出:
(停顿3秒)
A_one
B_two
3. 新增普通方法 getThree() , 打印? //three one two
代码:
public class _15_03_TestSynchronized {
public static void main(String[] args) throws Exception{
Number03 number = new Number03();
new Thread(() -> number.getOne(),"A").start();
Thread.sleep(50);
new Thread(() -> number.getTwo(),"B").start();
new Thread(() -> number.getThree(),"C").start();
}
}
class Number03{
public synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "_one");
}
public synchronized void getTwo(){//this
System.out.println(Thread.currentThread().getName() + "_two");
}
public void getThree(){
System.out.println(Thread.currentThread().getName() + "_Three");
}
}
输出:
C_Three
(停顿3秒)
A_one
B_two
4. 两个普通同步方法,两个 Number 对象,打印? //two one
public class _15_04_TestSynchronized {
public static void main(String[] args) throws Exception{
Number04 number = new Number04();
Number04 number2 = new Number04();
new Thread(() -> number.getOne(),"A").start();
Thread.sleep(50);
new Thread(() -> number2.getTwo(),"B").start();
}
}
class Number04{
public synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "_one");
}
public synchronized void getTwo(){//this
System.out.println(Thread.currentThread().getName() + "_two");
}
}
输出:
B_two
(停顿3秒)
A_one
5. 修改 getOne() 为静态同步方法,打印? //two one (重点)
代码:
public class _15_05_TestSynchronized {
public static void main(String[] args) throws Exception{
Number05 number = new Number05();
new Thread(() -> number.getOne(),"A").start();
Thread.sleep(50);
new Thread(() -> number.getTwo(),"B").start();
}
}
class Number05{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "_one");
}
public synchronized void getTwo(){//this
System.out.println(Thread.currentThread().getName() + "_two");
}
}
输出:
B_two
(停顿3秒)
A_one
6. 修改两个方法均为静态同步方法,一个 Number 对象? //one two
public class _15_06_TestSynchronized {
public static void main(String[] args) throws Exception{
Number06 number = new Number06();
new Thread(() -> number.getOne(),"A").start();
Thread.sleep(50);
new Thread(() -> number.getTwo(),"B").start();
}
}
class Number06{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "_one");
}
public static synchronized void getTwo(){//this
System.out.println(Thread.currentThread().getName() + "_two");
}
}
输出:
(停顿3秒)
A_one
B_two
7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象? //two one 对比5 (重点)
代码:
public class _15_07_TestSynchronized {
public static void main(String[] args) throws Exception{
Number07 number = new Number07();
Number07 number2 = new Number07();
new Thread(() -> number.getOne(),"A").start();
Thread.sleep(50);
new Thread(() -> number2.getTwo(),"B").start();
}
}
class Number07{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "_one");
}
public synchronized void getTwo(){//this
System.out.println(Thread.currentThread().getName() + "_two");
}
}
输出:
B_two
(停顿3秒)
A_one
8. 两个静态同步方法,两个 Number 对象? //one two 对比6
代码:
public class _15_08_TestSynchronized {
public static void main(String[] args) throws Exception{
Number08 number = new Number08();
Number08 number2 = new Number08();
new Thread(() -> number.getOne(),"A").start();
Thread.sleep(50);
new Thread(() -> number2.getTwo(),"B").start();
}
}
class Number08{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "_one");
}
public static synchronized void getTwo(){//this
System.out.println(Thread.currentThread().getName() + "_two");
}
}
输出:
(停顿3秒)
A_one
B_two
9. === 场景结论 ===
一、锁类型:(对象锁和类锁不互斥)
- static 类锁 (所有的静态同步方法用的也是同一把锁 – 类对象本身)
- this 对象锁 (所有的非静态同步方法用的都是同一把锁 – 实例对象本身)
二、this对象锁
一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能等待。(但是调用static的 synchronized方法不阻塞)
三、static 类锁
同一个类里面有多个 static synchronized 方法,某一个时刻内,只要一个线程去调用其中的一个static synchronized方法了,其他的线程调用static synchronized 都只能等待。(但是调用普通的 synchronized方法不阻塞)
四、普通方法与同步锁无关。(各走个的)
=== 点击查看top目录 ===
三、代码反编译 javap
javap的用法格式:
javap
其中classes就是你要反编译的class文件。
在命令行中直接输入javap或javap -help可以看到javap的options有如下选项:
-help --help -? 输出此用法消息
-version 版本信息,其实是当前javap所在jdk的版本信息,不是class在哪个jdk下生成的。
-v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类 和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-classpath <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
一般常用的是-v -l -c三个选项。
javap -v classxx,不仅会输出行号、本地变量表信息、反编译汇编代码,还会输出当前类用到的常量池等信息。
javap -l 会输出行号和本地变量表信息。
javap -c 会对当前class字节码进行反编译生成汇编代码。
四、Java对象头与Monitor
-
在hotspot虚拟机中,对象在内存中存储的布局可以分为3个区域:对象头(Header)、实例数据(Instance data)和对象填充(Padding)。
-
对象头(Header)包括两部分:
-
1、存储对象自身的运行时数据–Mark Word (如hashCode,GC分代年龄,锁状态标志,对象持有的锁,偏向线程ID,偏向时间戳)。
-
2、类型指针。虚拟机通过指针来确定这个对象是哪个类的实例。但并不是虚拟机实现都必须在对象类型上保留类型指针,换句话说,查找对象的元数据并不一定要经过对象本身。
-
-
实例数据 (Instance data):对象真正存储的有效信息。记录代码中无论是父类还是子类定义的各种类型的字段内容。记录的存储顺序受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在JAVA源码中的定义顺序的影响。HotSpot虚拟机默认的分配策略为:longs/doubles,ints,shorts/chars,bytes/booleans,oop(Ordinary Object Pointers)。在这前提下,父类定义的变量会出现在子类之前。如果CompactFields 参数为true(默认为true),那么子类之中较窄的变量可能会插入到父类变量的空隙之中。
-
对象填充(Padding): 并非必须存在,仅仅起占位符的作用,由于HotSpot VM 的自动内存管理要求对象起始地址必须是8字节的整体倍,换句话说,就是对象的大小必须是 8字节的整体倍。对象头正好是8的倍数,那么如果实例部分凑不上8字节的整体倍的话,就要来填充啦。
-
对象 = 对象头 + 数据部分。
对象头 = Mark Word + Class 指针。
HotSpot通过markOop类型实现Mark Word,具体实现位于markOop.hpp文件中。
考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间,如32位JVM下。
- 无锁状态 01
锁状态 | 25bit | 4bit | 1bit 是否是偏向锁 | 2bit 锁标志位 |
---|---|---|---|---|
无锁状态 | 对象HashCode | 对象分代年龄 | 0 | 01 |
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
- 其他状态(轻量级锁定00、重量级锁定10、GC标记11、可偏向01)
锁状态 | 30bit 锁内容 | 2bit 锁标志位 |
---|---|---|
轻量级锁 | 指向锁记录的指针 | 00 |
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 |
GC标记 | 空 | 11 |
可偏向锁 | 偏向线程ID(23bit)、偏向时间戳(2bit)、分代年龄(4bit)、是否偏向锁(1bit) | 01 |
// - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
// [ptr | 00] locked ptr points to real header on stack
// [header | 0 | 01] unlocked regular object header
// [ptr | 10] monitor inflated lock (header is wapped out)
// [ptr | 11] marked used by markSweep to mark an object
// not valid at any other time
- 无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。
五、synchronized 底层分析
- Synchronzied 三种使用方法:
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前 Instance 的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前 Class 的锁
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
5.1 代码块 demo
TestBlock.java
public class TestBlock {
public void test01(){
synchronized(this){
int a = 1;
}
}
}
javac TestBlock.java
反编译看一下
javap -c -l -v TestBlock
Classfile /Users/momo/Downloads/TestBlock.class
Last modified Sep 14, 2019; size 358 bytes
MD5 checksum 5c8519109a71ce4098170f7224fc8b90
Compiled from "TestBlock.java"
public class TestBlock
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#15 // java/lang/Object."<init>":()V
#2 = Class #16 // TestBlock
#3 = Class #17 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 test01
#9 = Utf8 StackMapTable
#10 = Class #16 // TestBlock
#11 = Class #17 // java/lang/Object
#12 = Class #18 // java/lang/Throwable
#13 = Utf8 SourceFile
#14 = Utf8 TestBlock.java
#15 = NameAndType #4:#5 // "<init>":()V
#16 = Utf8 TestBlock
#17 = Utf8 java/lang/Object
#18 = Utf8 java/lang/Throwable
{
public TestBlock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 2: 0
public void test01();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter // 注意!!! 进入同步方法
4: iconst_1
5: istore_2
6: aload_1
7: monitorexit // 注意!!! 退出同步方法
8: goto 16 // 能跑到这里说明程序正常运行,goto到16进行return
11: astore_3
12: aload_1
13: monitorexit // 注意!!! 退出同步方法,跑到这里说明抛异常了。
14: aload_3
15: athrow
16: return
Exception table:
from to target type
4 8 11 any
11 14 11 any
LineNumberTable:
line 4: 0
line 5: 4
line 6: 6
line 7: 16
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 11
locals = [ class TestBlock, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
SourceFile: "TestBlock.java"
这里我们看一下关键代码:
3: monitorenter // 注意!!! 进入同步方法
...
7: monitorexit // 注意!!! 退出同步方法
8: goto 16 // 能跑到这里说明程序正常运行,goto到16进行return
...
13: monitorexit // 注意!!! 退出同步方法,跑到这里说明抛异常了。
从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置。
5.2 monitorenter 源码
1. 偏向锁获取
- 下面开始偏向锁获取流程分析,代码在 bytecodeInterpreter.cpp#1816。注意本文代码都有所删减。
CASE(_monitorenter): {
// lockee 就是锁对象
oop lockee = STACK_OBJECT(-1);
// derefing's lockee ought to provoke implicit null check
CHECK_NULL(lockee);
// code 1:找到一个空闲的Lock Record
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
BasicObjectLock* entry = NULL;
while (most_recent != limit ) {
if (most_recent->obj() == NULL) entry = most_recent;
else if (most_recent->obj() == lockee) break;
most_recent++;
}
//entry不为null,代表还有空闲的Lock Record
if (entry != NULL) {
// code 2:将Lock Record的obj指针指向锁对象
entry->set_obj(lockee);
int success = false;
uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
// markoop即对象头的mark word
markOop mark = lockee->mark();
intptr_t hash = (intptr_t) markOopDesc::no_hash;
// code 3:如果锁对象的mark word的状态是偏向模式
if (mark->has_bias_pattern()) {
uintptr_t thread_ident;
uintptr_t anticipated_bias_locking_value;
thread_ident = (uintptr_t)istate->thread();
// code 4:这里有几步操作,下文分析
anticipated_bias_locking_value =
(((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
~((uintptr_t) markOopDesc::age_mask_in_place);
// code 5:如果偏向的线程是自己且epoch等于class的epoch
if (anticipated_bias_locking_value == 0) {
// already biased towards this thread, nothing to do
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
}
// code 6:如果偏向模式关闭,则尝试撤销偏向锁
else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// 利用CAS操作将mark word替换为class中的mark word
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
}
// code 7:如果epoch不等于class中的epoch,则尝试重偏向
else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
// 构造一个偏向当前线程的mark word
markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
if (hash != markOopDesc::no_hash) {
new_header = new_header->copy_set_hash(hash);
}
// CAS替换对象头的mark word
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(* BiasedLocking::rebiased_lock_entry_count_addr())++;
}
else {
// 重偏向失败,代表存在多线程竞争,则调用monitorenter方法进行锁升级
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
else {
// 走到这里说明当前要么偏向别的线程,要么是匿名偏向(即没有偏向任何线程)
// code 8:下面构建一个匿名偏向的mark word,尝试用CAS指令替换掉锁对象的mark word
markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |(uintptr_t)markOopDesc::age_mask_in_place |epoch_mask_in_place));
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// debugging hint
DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
// CAS修改成功
if (PrintBiasedLockingStatistics)
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
else {
// 如果修改失败说明存在多线程竞争,所以进入monitorenter方法
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
}
// 如果偏向线程不是当前线程或没有开启偏向模式等原因都会导致success==false
if (!success) {
// 轻量级锁的逻辑
//code 9: 构造一个无锁状态的Displaced Mark Word,并将Lock Record的lock指向它
markOop displaced = lockee->mark()->set_unlocked();
entry->lock()->set_displaced_header(displaced);
//如果指定了-XX:+UseHeavyMonitors,则call_vm=true,代表禁用偏向锁和轻量级锁
bool call_vm = UseHeavyMonitors;
// 利用CAS将对象头的mark word替换为指向Lock Record的指针
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
// 判断是不是锁重入
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) { //code 10: 如果是锁重入,则直接将Displaced Mark Word设置为null
entry->lock()->set_displaced_header(NULL);
} else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
} else {
// lock record不够,重新执行
istate->set_msg(more_monitors);
UPDATE_PC_AND_RETURN(0); // Re-execute
}
}
以上是偏向锁加锁的流程(包括部分轻量级锁的加锁流程),如果当前锁已偏向其他线程||epoch值过期||偏向模式关闭||获取偏向锁的过程中存在并发冲突,都会进入到InterpreterRuntime::monitorenter方法, 在该方法中会对偏向锁撤销和升级。
2.偏向锁撤销和升级
查看 interpreterRuntime.cpp ,内部的 InterpreterRuntime::monitorenter 方法:
翻译: BiasedLock -> 偏向锁
//%note monitor_1
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (UseBiasedLocking) { //重点看这里!是否使用偏向锁
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
"must be NULL or an object");
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
- 看下 Input 入参
JavaThread : 指向java中的当前线程
BasicObjectLock :
class BasicObjectLock {
BasicLock _lock;
// object holds the lock;
oop _obj;
}
class BasicLock {
//主要用来保存_obj指向Object对象的对象头数据
volatile markOop _displaced_header;
}
- 看下关键代码
if (UseBiasedLocking) { //重点看这里!是否使用偏向锁
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
-
看下判断条件 UseBiasedLocking
UseBiasedLocking标识虚拟机是否开启偏向锁功能,如果开启则执行fast_enter逻辑,否则执行slow_enter; -
fast_enter
ObjectSynchronizer::fast_enter位于 synchronizer.cpp 文件内部。
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
如果不在安全点上,我们就进入偏向。我们从revoke_and_rebias(翻译:撤销或者重偏向)这个名字可以看出,偏向锁就是在没有多线程竞争的情况下,减少不必要的锁执行路径,只是更新了一下线程Id(撤销一个,偏向另外一个)。
备注:JVM中的每个类也有一个类似mark word的prototype_header,用来标记该class的epoch和偏向开关等信息。上面的代码中lockee->klass()->prototype_header()即获取class的prototype_header。
- 看下 revoke_and_rebias 方法
位于:biasedLocking.cpp
BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");
// ===== 1、通过markOop mark = obj->mark()获取对象的markOop数据mark,即对象头的Mark Word
markOop mark = obj->mark();
// ===== 2、判断mark是否为可偏向状态,即mark的偏向锁标志位为 1,锁标志位为 01;
if (mark->is_biased_anonymously() && !attempt_rebias) {
//如果是匿名偏向且attempt_rebias==false会走到这里,如锁对象的hashcode方法被调用会出现这种情况,需要撤销偏向锁。
markOop biased_value = mark;
//prootype本身构建的是 markOop( no_hash_in_place | no_lock_in_place );
markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
//执行CAS,如果当前对象的mark没有变更,就换成 unbiased_prototype
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
//如果之前的和现在的一样,说明撤销成功,BIAS_REVOKED本身是一个枚举
return BIAS_REVOKED;
}
} else if (mark->has_bias_pattern()) { // ===== 4、锁对象开启了偏向模式会走到这里
//已经被线程偏向了,获取Klass对象,即类本身的头,obj则是它的实例
Klass* k = Klass::cast(obj->klass());
markOop prototype_header = k->prototype_header();
if (!prototype_header->has_bias_pattern()) {//code 1: 如果对应class关闭了偏向模式
//直接设置成已经撤销偏向即可
markOop biased_value = mark;
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);
assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");
return BIAS_REVOKED;
} else if (prototype_header->bias_epoch() != mark->bias_epoch()) { //code2: 如果epoch过期
//实例的epoch和类本身的epoch值不一样,说明它已经过期,也就是说这个对象当前处于未偏向但是可偏向的状态(rebiasable)
if (attempt_rebias) {
//执行rebias wait希望直接撤销
assert(THREAD->is_Java_thread(), "");
markOop biased_value = mark;
markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) { //当前线程抢到了这个对象的偏向
return BIAS_REVOKED_AND_REBIASED;
}
} else {
markOop biased_value = mark;
markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
//CAS撤销偏向锁
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
//撤销了偏向
return BIAS_REVOKED;
}
}
}
}
//===== 5:没有执行偏向,通过启发式的方式决定到底是执行撤销还是执行rebias
HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);
if (heuristics == HR_NOT_BIASED) { //5.1:偏向状态改成了不需要偏向
return NOT_BIASED; //不可偏向直接返回
} else if (heuristics == HR_SINGLE_REVOKE) {
//5.2:启发式决定执行单次的撤销
Klass *k = Klass::cast(obj->klass());
markOop prototype_header = k->prototype_header();
if (mark->biased_locker() == THREAD &&
prototype_header->bias_epoch() == mark->bias_epoch()) {
// 走到这里说明需要撤销的是偏向当前线程的锁,当调用Object#hashcode方法时会走到这一步
// 因为只要遍历当前线程的栈就好了,所以不需要等到safepoint再撤销。
ResourceMark rm;
if (TraceBiasedLocking) {
tty->print_cr("Revoking bias by walking my own stack:");
}
BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);
((JavaThread*) THREAD)->set_cached_monitor_info(NULL);
assert(cond == BIAS_REVOKED, "why not?");
return cond;
} else {
// 下面代码最终会在VM线程中的safepoint调用revoke_bias方法
VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
VMThread::execute(&revoke);
return revoke.status_code();
}
}
assert((heuristics == HR_BULK_REVOKE) ||
(heuristics == HR_BULK_REBIAS), "?");
//6:等到虚拟机运行到safepoint,实际就是执行 VM_BulkRevokeBias 的doit的 bulk_revoke_or_rebias_at_safepoint方法
//批量撤销、批量重偏向的逻辑
VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
(heuristics == HR_BULK_REBIAS),
attempt_rebias);
VMThread::execute(&bulk_revoke);
return bulk_revoke.status_code();
}
- 看下 is_biased_anonymously 方法
is_biased_anonymously方法判断是否为可偏向状态,mark的偏向锁标志位为1,锁标识位为01。
具体分析如下:
位于:markOop.hpp
bool is_biased_anonymously() const {
return (has_bias_pattern() && (biased_locker() == NULL));
}
- 看下 has_bias_pattern 方法
位于:markOop.hpp
bool has_bias_pattern() const {
// //这里后面的 biased_lock_pattern值是5,二级制101。(mark的偏向锁标志位为1,锁标识位为01。)
return (mask_bits(value(), biased_lock_mask_in_place) == biased_lock_pattern);
}
- biased_lock_pattern 的值
位于:markOop.hpp
enum { locked_value = 0,
unlocked_value = 1,
monitor_value = 2,
marked_value = 3,
biased_lock_pattern = 5
};
- slow_enter
ObjectSynchronizer::slow_enter 位于 synchronizer.cpp 文件内部。
// -----------------------------------------------------------------------------
// Interpreter/Compiler Slow Case
// This routine is used to handle interpreter/compiler slow case
// We don't need to use fast path here, because it must have been
// failed in the interpreter/compiler code.
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
if (mark->is_neutral()) {
// Anticipate successful CAS -- the ST of the displaced mark must
// be visible <= the ST performed by the CAS.
lock->set_displaced_header(mark);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
// Fall through to inflate() ...
} else
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
lock->set_displaced_header(NULL);
return;
}
总结一下:
偏向锁获取的过程如下,当锁对象第一次被线程获取的时候,虚拟机把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中的偏向线程ID,并将是否偏向锁的状态位置置为1。如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,直接检查ThreadId是否和自身线程Id一致,
如果一致,则认为当前线程已经获取了锁,虚拟机就可以不再进行任何同步操作(例如Locking、Unlocking及对Mark Word的Update等)。
其实一般来说偏向锁很少又说去主动释放的,因为只有在其他线程需要获取锁的时候,也就是这个锁不仅仅被一个线程使用,可能有两个线程交替使用,根据对象是否被锁定来决定释放锁(恢复到未锁定状态)还是升级到轻量锁状态。
六、锁优化
锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级,关于重量级锁,前面我们已详细分析过,下面我们将介绍偏向锁和轻量级锁以及JVM的其他优化手段,这里并不打算深入到每个锁的实现和转换过程更多地是阐述Java虚拟机所提供的每个锁的核心优化思想,毕竟涉及到具体过程比较繁琐,如需了解详细过程可以查阅《深入理解Java虚拟机原理》。
- 偏向锁
偏向锁是Java 6之后加入的新锁,它是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。下面我们接着了解轻量级锁。
tip: 作者加注:公司有一部电梯,只供CEO或者特殊情况应急用,那么就满足上面的在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。
- 轻量级锁
倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”(我用完的时候,别人才会刚好过来拿),注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
tip: 作者加注:在富士康工厂的三班倒状态下,每一台机器,都会轮流给不同的工人使用,所以在整个同步周期内不存在竞争。
3. 自旋锁 (耗CPU)
轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的。最后没办法也就只能升级为重量级锁了。
因为许多情况下,共享数据的锁定状态持续时间较短,切换线程不值得,通过让线程执行循环等待锁的释放,不让出CPU。如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式。但是它也存在缺点:如果锁被其他线程长时间占用,一直不释放CPU,会带来许多的性能开销。
自适应自旋锁:这种相当于是对上面自旋锁优化方式的进一步优化,它的自旋的次数不再固定,其自旋的次数由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定,这就解决了自旋锁带来的缺点。
tip: 作者加注:一层楼有一个厕所,厕所有5个坑,坑被占据了之后,上厕所的人就会先跑去写代码,然后写一阵子又跑回来找坑,跑来跑去循环跑。
- 锁消除
消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如下StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。
demo1:
public void elimination01(){
Object object = new Object();
synchronized (object){
System.out.println("hello world ...");
}
}
// 执行效率是一样的,因为object锁是私有变量,不存在所得竞争关系。
public void elimination02(){
Object object = new Object();
System.out.println("hello world ...");
}
demo2:
public void elimination03(){
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
}
- 看下 append 方法
@Override
public synchronized StringBuffer StringBuffer#append(java.lang.String)(String str) {
toStringCache = null;
super.append(str);
return this;
}
由于 stringBuffer 是局部变量,不存在竞争关系,所以内部的 synchronized 会被消除掉。
- 锁粗化(Lock Coarsening):锁粗化的概念应该比较好理解,就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁。举个例子:
// =========== 锁粗化 ===========
public void coarsening01() {
for (int i = 0; i < 100000; i++) {
synchronized (this) {
System.out.println("hello world ...");
}
}
}
// 锁粗化:通过扩大锁的范围,避免反复加锁和释放锁。
public void coarsening02() {
synchronized (this) {
for (int i = 0; i < 100000; i++) {
System.out.println("hello world ...");
}
}
}
- 重量级锁(尽量避免)
Synchronized是通过对象内部的一个叫做 监视器锁(Monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为 “重量级锁”。
七、番外篇
下一章节:【线程】CountDownLatch 内部原理(十三)
上一章节:【线程】ReentrantReadWriteLock 内部共享锁与排他锁源码剖析 (十一)