Java锁与synchronized

Java 锁与 synchronized

Java 内置锁 synchronized 是一把互斥锁, 这意味这同时只有一个线程能够进入锁

锁的概念

  1. 自旋锁: 为了不放弃 CPU 执行事件,循环的使用 CAS 技术对数据尝试进行更新,直到成功,本质上是乐观锁的一种实现
  2. 悲观锁: 假定会发生并发冲突,同步所有对数据的相关的操作,从读数据就开始上锁
  3. 乐观锁: 假定没有冲突,在修改数据时如果发现数据和之前不一致,则读最新数据,再重试修改
  4. 独享锁(写): 给资源加上写锁,只有一个线程可以修改,其他线程排队
  5. 共享锁(读): 给资源加上读锁,其他线程只能加读锁,不能加写锁
  6. 可重入锁、不可重入锁: 线程拿到锁后可以、不可以重用锁资源
  7. 公平锁、不公平锁: 争抢锁的顺序,如先来后到则是公平锁

注意:不同的锁应对不同的场景,没有优劣之分,只有最适合场景的锁.

同步关键字 synchronized

Java 内置锁 synchronized
同步关键字 synchronized 属于悲观锁
属于最基本的线程通信机制,基于对象监视器实现的。
Java 中的每个对象都与一个监视器相关联,
一个线程可以锁定或解锁监视器,一次只有一个线程可以锁定监视器
特性:synchronized 是一个可重入、独享、悲观锁

synchronized 用于同步方法和代码块,执行完后自动释放锁

锁的范围:

修饰类或静态方法,类锁, 同步的内容是 class 对象和静态方法

修饰非静态方法或者 this,对象锁,同步的内容是单个对象资源

synchronized 原理

理解 Java 对象头与 Monitor

对象在内存中,有一块对象头区域,它是实现 synchronized 的基础,

对象头区域有两个部分:
第一部分(markword),存放hashCode 值锁状态GC 标志等信息
第二部分,存放类型指针,指向类元数据,表名该对象属于哪个类

第一部分除了锁状态,Java6 还新增了轻量级锁和偏向锁。
synchronized 属于重量级锁

锁状态

对象头中的锁状态,存放这锁的信息

  1. 有一个线程 ID
    是标记线程对象的
    线程中也有一个锁记录,来标记到锁

  2. 有一个锁状态位
    标记当前锁的状态
    01:unlocked(未锁定)
    00:轻量级锁
    10:重量级锁
    11:要被 GC 回收
    线程可以通过 CAS 机制来修改锁的状态位

  3. 有一个偏向锁锁状态位
    偏向锁锁状态位只有一位
    0/1: 开启或者关闭

  4. 有一个锁监视器标志位
    锁监视器标志位中指针指向Monitor 对象(锁监视器)的起始地址
    每个对象都有一个Monitor 对象(锁监视器)与其关联

Monitor 对象(锁监视器)

Monitor 对象(锁监视器)中有一个指针,指向持有它的线程(_owner)
Monitor 对象还拥有两个队列, 一个是线程排队队列(_EntryList),一个是线程阻塞队列(_WaitList)

当持有它的对象释放它的时候,指针会重制,指向等待队列中的下一个线程,

当线程执行 wait 时,它会进入阻塞队列_WaitList,唤醒后,进入排队队列_EntryList

synchronized 就是通过这种方法来实现锁的, 这也是为什么 Java 中任意对象都可以是锁的原因。
监视器跟对象共同的生命周期,但是在没有开启重量级锁之前,监视器是空对象,_owner 在开启重量级锁后才会指向线程

偏向锁、轻量级锁、自旋锁、锁消除

偏向锁
偏向锁就是,当一个线程获得了锁,就会进入偏向模式,当这个线程再次请求锁时,无需再做任何同步操作,即可获取锁(本质可以理解为单线程情况无锁)
(因为当只有一个线程反复操作锁的时候,不应该反复加锁, 这是额外的消耗,是需要尽量降低的)

当一个线程来获取锁,并不会第一时间修改锁状态位,而是先修改偏向锁的标志位,偏向锁默认为 1 即开启状态,还有一个线程标记,指向获取偏向锁的线程,线程要获取偏向锁,修改线程标记指向自己, 操作成功即得到锁,第二次再来访问锁时,比较当前的线程标记是否是自己,如果是,就直接访问. 但如果是第二个线程想要来获取锁,发现线程标记不是自己,就会锁升级到轻量级锁,升级后,偏向锁改为 0,即关闭偏向锁

但是对于锁竞争比较激烈的场合,偏向锁就失效了,当每次申请锁的线程都是不相同的,每个线程都获得了偏向锁,就没有了偏向锁的效果了

偏向锁失败后,会升级为轻量级锁

轻量级锁
当一个线程进入锁,有线程来争抢锁时,偏向锁升级为轻量级锁
来争抢的线程会进入自旋,即循坏等待锁被释放,即自旋锁

轻量级锁所适应的场景是线程交替执行同步块的场合

如果存在同一时间多个线程访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

重量级锁
重量级锁即 synchronized 实现的锁, 启用锁监视器 Monitor 对象
线程自旋次数增加后升级到重量级锁, 停止自旋, 监视器启动, 监视器中的_owner 属性指向当前占有锁的线程,

锁升级是单向的,即从低到高,从轻到重,锁的等级是通过对象中的锁信息里的状态位来标识,而多线程争抢锁,就会改变这个状态位

锁消除
锁消除即消除不必要的锁,或者缩小锁的范围