前言
synchronized是阻塞锁,非公平锁。
java中一个对象包含:
- 对象头:Mark Word 和 Class Pointer Address(对应类信息的klass信息)
- 对象中的实际数据:instance data
- 对齐填充: padding
锁的状态:
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
对于一个对象内存中的具体数据分配,添加工具jol使用ClassLayout打印内存信息。
implementation 'org.openjdk.jol:jol-core:0.14'
public static String test(){
Test test = new Test();
return ClassLayout.parseInstance(test).toPrintable();
}
输出:
Test:com.georege.mylearn.Test object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 4e 36 03 20 (01001110 00110110 00000011 00100000) (537081422)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
当第一个线程调用synchronized这个对象的时候,对象状态变成偏向锁,带上当前线程的id,又来一个线程,会变成轻量级锁,带上指向栈中锁积累(Lock Record)的指针,又来一个线程,变成重量级锁,此时指向互斥量(Monitor)的指针。
synchronized代码块都有一个monitorenter和monitorexit, monitor-enter v1和monitor-exit v1
synchronized关键字依托于对象头的markWord中锁的状态信息,实现线程的并发同步操作,有4种锁的状态,synchronized再最新java中已经有相当好的性能。在属于轻量级锁的时候,等待线程会自选等待,当有第3个线程进来,或者自旋一定数量(根据jvm的优化判断是需要自旋转或直接升级重量级锁),就会升级为重量级锁,重量级锁通过Monitor变量来维护等待线程队列。当等待线程等待一定时间或者own_thread退出解锁的时候,等待队列被唤醒,进入唤醒队列,唤醒队列中的各个线程通过竞争获取锁,获得成功就变成own。不成功继续进入等待队列,等待下一次唤醒。所以synchronized最终效果是阻塞和非公平锁。
synchronized关键字字节码指令是MonitorEnter,MonitorExt。当一个对象没有锁的时候,synchronized触发获取锁,会将对象头的markWord中的信息锁状态变成偏向锁,又来一个线程获取锁,该对象会升级为轻量级锁,这个线程会自旋等待,直到获取锁,又来一个线程或者自旋一定数量以后,该对象会升级为重量级锁状态,重量级锁状态指向一个Monitor对象指针,Monitor维护线程等待队列,处于等待队列的线程也处于阻塞状态,当等待线程等待一定时间或者own_thread退出解锁的时候,等待队列被唤醒,进入唤醒队列,唤醒队列中的各个线程通过竞争获得锁,获得成功就变成own。不成功继续进入等待队列继续Blocked,等待下一次唤醒。synchronized是阻塞锁和非公平锁。
一个对象没有锁->synchronized触发获取锁->对象头变成偏向锁->又来一个线程2->轻量级锁->自旋等待->又来一个线程3或自旋一定数量后->重量级锁。
synchronized可能导致的场景问题:
- 阻塞主线程。synchronized异步操作数据集合。
- synchronized都是锁的对象klass
- 如果是普通类,锁的对象klass会有隐患,使用普通类的成员变量obj锁即可,除非是单例可以使用。
- 使用synchronized(this)和非静态方法synchronized是一样的,都是同一个对象
- 尽量不适用嵌套synchronized,可能触发死锁。