Synchronized剖析
Object o = new Object();
synchronizerd对象头的锁
synchronized的信息是记录在对象头中
MarkWord
中
synchronized锁升级过程
new-->偏向锁-->轻量级锁(无锁,自旋锁,自适应锁)-->重量级锁
synchronized优化的过程和markdown息息相关
用Markdown中最低的3位代表锁状态,其中1位是锁偏向状态,两位是普通锁位
偏向锁(只有一个线程访问锁)
偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。
轻量级锁/自旋锁(多个线程抢)
- 如果多个锁竞争资源则偏向锁失效,虚拟机并不会立即挂起线程,它还会使用一种称为轻量级锁的优化手段。轻量级锁的操作也很轻便,它只是简单的将对象头部作为指针指向持有锁的线程堆栈的内部,来判断一个线程是否持有锁。
- 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为
自旋锁
的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的。最后没办法也就只能升级为重量级锁了。- 线程进行抢锁,就升级为轻量级锁;这里自旋,处于用户态
每个线程在自己的栈帧中生成一个Lock Record
然后在while()循环中自旋的获取锁
,若获取则将线程栈中的指针指向该线程的Lock Record
;其余的线程自旋去抢,但是若自旋的次数特别多,则浪费CPU,当自旋到一定次数的时候,则会再次升级为重量级锁
重量级锁(用户太升级到内核态,操作系统互斥量)
当竞争加剧,自旋的次数特别多还没有获取到轻量级锁的时候,则升级到这种锁,向CPU申请互斥量:重量级锁
;
互斥量是内核态中互斥的数据机构,申请重量锁即锁的指针指向的是内核态中的指针
锁消除
消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间
锁粗化
synchronized底层实现原理剖析
java代码层级:synchronized
- 同步方法块: 对给定对象加锁,进入同步代码前需要获得给定对象的锁
- 同步方法: 锁是当前实例对象
- 静态同步方法:锁是自己定义的锁对象
jvm字节码层级:monitorenter和monirorexit指令
monitorenter
指令插入到同步代码块的开始位置,monitorexit
指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor(在对象头中)与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;
jvm执行过程中锁升级:
偏向级锁-->轻量级锁(自旋锁)---->重量级锁
CPU汇编层级:lock comxchg
底层机器吗最终还是Lock cmpxgahage
(和volitale是一样的)
volitale
计算机基础
缓存一致性,系统底层如何实现数据一致性
- 缓存一致性协议能解决,就使用MESI
- 如果不能,就锁总线
程序顺序性/乱序执行:系统底层如何保证有序性
内存屏障
:sfence mfence lfence等系统原语- 锁总线
volitale如何解决指令重排序
java层 voiliate i
volitale ing i
字节码层级
ACC_VOLATILE
就是一个标志
虚拟机规范
内存屏障两边额指令不快乐同一重排,保证有序
hotspot实现
最终还是用的
lock
指令,和synchronized,cas一致
ThreadLocal
四种引用
软引用
SoftRerence <byte[]> m = new Reference<>(new byte[1024*1024]);
当内存不够时,软引用的对象会回收。
缓存中用的比较多。
弱引用WeakReference(ThreadLocal)
只要垃圾回收就回收
虚引用(管理堆外内存)
PhantomReference<M> phantomReference = new PhantomReferecne<>(new M(),QUEUE);
作用:管理堆外内存
ThreadLocal介绍
绑定在每个线程上的对象
为什么是弱引用????(
内存泄露
)
一旦外部对ThreadLocal的引用消失
,则Entry
指向的ThreadLocal
因为是弱引用
则可以对ThreadLocal
回收,此时key
为null,我们需要手动进行remove()
方法
应用
Spring的@Transcational注解中,多个数据库连接确保是一样的需要使用
ThreadLocal