一 synchronized 使用
1. 对象锁:
a. 同步代码块synchronized(this),synchronized(类实例对象),锁是小括号中的实例对象
b. 同步非静态方法synchronized method,锁是当前对象的实例对象
2. 类锁:
a. 同步代码块Synchronized(类.class),锁是小括号中类的对象
b. 同步静态方法Synchronized static method,锁是当前对象的类对象
3. 例子
二synchronized实现原理
Java对象头+ Monitor
2.1 对象的内存布局 =对象头+实例数据+对齐填充
2.1.1 对象头分类
i. markWord 默认存储对象的hashCode,分代年龄,锁类型,锁标志位等信息
ii. Class metadata Address 类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的数据
2.1.2 markword
对象需要存储的运行时数据很多,其实已经超过了32位、64为bitmap结构所能记录的限度,但是对象头是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,markword被设计成一个非固定的数据结构以便在极小的空间存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如,在32位的hotSpot虚拟机中,举个例子,如下图第一行,如果对象处于未被锁定的状态下,那么mark word的32位空间中的25bit用于存储对象哈希码,4bit用于存储对象的分代年龄,2bit用于存储锁标志位,1bit固定为0,而在其他状态可见下表:
2.2 Monitor(监视,班长):每个java对象天生自带了一把看不见的锁,指向重量级锁的指针就是指向ObjectMonitor-见源码
2.3 重入
从互斥锁的设计来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入。
2.4 为什么会对synchronized不友好?
早期版本中,synchronized属于重量级锁(Monitor锁),依赖于底层Mutex Lock实现;
线程之间的切换需要从用户状态转换到核心态,开销较大;
Java6之后,Synchronized性能得到了很大提升。
2.5.自旋锁:
a. 许多情况下,共享数据的锁定状态持续时间短,切换线程不值得
b. 通过让线程执行忙循环等待锁释放,不让出cpu
c. 缺点:若锁被其他线程长时间占用,会带来许多性能上的开销
d. preBlockSpin参数更改,jdk1.6,默认启用,默认情况下自旋的次数是 10 次, 可以通过 -XX:PreBlockSpin=10来修改。
2.6 自适应自旋锁 jdk1.6引入
a. 自旋的次数不再固定
b. 由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
2.7 锁消除-更彻底的优化
a. JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁
2.8 锁粗化
a. 扩大加锁的范围,避免反复加锁和解锁
2.9 synchronized的四种状态
2.9.1 无锁 偏向锁 轻量级锁 重量级锁
2.9.2 锁膨胀状态 无锁->偏向锁->轻量级锁->重量级锁
2.9.3 偏向锁:减少同一线程获取锁的代价
i. 大多数情况下,锁不存在多线程竞争,总是由同一个线程多次获得
ii. 核心思想:如果一个线程获得了锁,那么锁就进入偏向模式,此时mark word的结构也变为偏向锁结构,当该线程再次获取请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查mark word锁标记位为偏向锁以及当前线程Id等于Mark Word的ThreadId即可,这样就省去了大量有关锁申请的操作。
iii. 不适用于锁竞争比较激烈的多线程
2.9.4 轻量级锁:是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。
i. 适应场景:线程交替执行同步块
ii. 若存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁
iii. 轻量级加锁:线程在执行同步块之前,JVM会先在当前栈帧中创建用于存储锁记录的空间,并将对象中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用 CAS 将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为”10”,Mark Word中存储的就是指向重量级(互斥量)的指针。
iv. 轻量级锁解锁:轻量级解锁时,会使用原子的 CAS 操作来将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
2.9.5 锁内存含义
当线程释放锁时,Java内训模型会把该线程对应的本地内存中的共享变量刷新到主内存中;而当线程获取锁时,Java内存模型会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。