整篇文章从线程安全的诱因着手,探讨synchronized具体实现方式,期间简要穿插STW、CAS、JDK6后的锁革命,后续篇章再详尽对其介绍。
线程安全诱因:
- 临界数据(存在共享的数据)
- 多线程同时操作临界数据
Mutually exclusive(互斥机制,中央集权)
互斥锁:保证同一时刻,只有一个线程在操作共享数据,期间其余竞争进程等待其完成。
互斥锁可以很好的解决线程安全方面的问题,但由于期间线程霸占对象所有的共享资源,其属独裁。
而Java中的每个对象,都拥有自己内置的互斥锁 synchronized。
Every Java object has an intrinsic lock (or a monitor lock) associated with it.
并保证内存的可见性,保证可见性方面可替代volatile。
Synchronize serializes access for what is locked and guarantee memory visibility for the changes that happened inside the synchronized scope to all threads.
Lock ancestor, two level (始祖锁,两种级别)
synchronized 只提供两种级别的锁:
- 对象锁(修饰实例方法、修饰代码块);
- 类锁(修饰静态方法、修饰代码块);
以下4个代码片段,展示基础用法:
// Already holds a lock when called [修饰实例方法]
public synchronized void increase4Obj() {
i++;
}
// It's possible to lock only a block inside the method[修饰代码块(对象)]
public synchronized void increase4ObjBlock() {
synchronized(this) {
i++;
}
}
// 修饰静态方法
public static synchronized void increase4Static() {
i++;
}
// 修饰代码块(类)
public void increase4StaticBlock() {
synchronized(staticObj.class) {
i++;
}
}
demo可参见:https://github.com/ah-ivy/asynchronouse/blob/master/src/main/java/com/company/intrinsic/SyncClass.java/
警示点
- When used in method signature, synchronized use ‘this’ as a lock(加在实例方法上,与代码块相同,底层使用this对象进行加锁).
- 对象锁属于当前实例,不同实例锁互相独立。
- 一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized实例方法,但是其他线程还是可以访问该实例对象的其他非synchronized方法。
- synchronized不能被继承。
- Intrinsic locks are reentrant: if you are holding it, you can acquire it again, without deadlocking(synchronized属于可重入锁,可嵌套调用,避免死锁).
- 类中同时存在对象锁与类锁,操作同一共享数据,则可能发生线程安全问题。
Realization(具体实现)
synchronized的实现主要依赖两个基石(下一篇章有详尽分析,下文仅带过):
- Monitor对象的进入(monitorenter)与退出(monitorexit)。(显式同步)
- 运行时常量池中的ACC_SYNCHRONIZED。(隐式同步)
synchronized修饰的同步代码块,通过对象头里的mark word中的重量级锁指向monitor对象起始位置,每个对象都存在着一个 monitor 与之关联,当一个 monitor 被某个线程持有后,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1,字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。
synchronized所修饰的同步方法,无需通过字节码指令控制,JVM使用ACC_SYNCHRONIZED标志位区分,如果设置了,执行线程先获取monitor对象,在执行方法,结束时释放monitor对象。
Efficiency bottleneck (效率瓶颈)
synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized效率低的原因。
jdk6开始,偏向锁与轻量锁的引入,让这个世界多了些温馨。
在java的锁世界中: 无锁状态 -> 偏向锁 -> 轻量级锁 -> 重量级锁
在当前锁条件不满足时,便发生了锁的升级,关于锁是否会降级,网上太部分观点是不发生降级,但锁确实会发生降级,在safe point(安全点)时,可触发锁的降级。让人颤抖的stop the world(STW)正在此时发生。下一篇章将接受锁的这部分世界。