Java 锁与 synchronized
Java 内置锁 synchronized 是一把互斥锁, 这意味这同时只有一个线程能够进入锁
锁的概念
- 自旋锁: 为了不放弃 CPU 执行事件,循环的使用 CAS 技术对数据尝试进行更新,直到成功,本质上是乐观锁的一种实现
- 悲观锁: 假定会发生并发冲突,同步所有对数据的相关的操作,从读数据就开始上锁
- 乐观锁: 假定没有冲突,在修改数据时如果发现数据和之前不一致,则读最新数据,再重试修改
- 独享锁(写): 给资源加上写锁,只有一个线程可以修改,其他线程排队
- 共享锁(读): 给资源加上读锁,其他线程只能加读锁,不能加写锁
- 可重入锁、不可重入锁: 线程拿到锁后可以、不可以重用锁资源
- 公平锁、不公平锁: 争抢锁的顺序,如先来后到则是公平锁
注意:不同的锁应对不同的场景,没有优劣之分,只有最适合场景的锁.
同步关键字 synchronized
Java 内置锁 synchronized
同步关键字 synchronized 属于悲观锁
属于最基本的线程通信机制,基于对象监视器实现的。
Java 中的每个对象都与一个监视器相关联,
一个线程可以锁定或解锁监视器,一次只有一个线程可以锁定监视器
特性:synchronized 是一个可重入、独享、悲观锁
synchronized 用于同步方法和代码块,执行完后自动释放锁
锁的范围:
修饰类或静态方法,类锁, 同步的内容是 class 对象和静态方法
修饰非静态方法或者 this,对象锁,同步的内容是单个对象资源
synchronized 原理
理解 Java 对象头与 Monitor
对象在内存中,有一块对象头区域,它是实现 synchronized 的基础,
对象头区域有两个部分:
第一部分(markword),存放hashCode 值、锁状态、GC 标志等信息
第二部分,存放类型指针,指向类元数据,表名该对象属于哪个类
第一部分除了锁状态,Java6 还新增了轻量级锁和偏向锁。
synchronized 属于重量级锁
锁状态
对象头中的锁状态,存放这锁的信息
有一个线程 ID
是标记线程对象的
线程中也有一个锁记录,来标记到锁有一个锁状态位
标记当前锁的状态
01:unlocked(未锁定)
00:轻量级锁
10:重量级锁
11:要被 GC 回收
线程可以通过 CAS 机制来修改锁的状态位有一个偏向锁锁状态位
偏向锁锁状态位只有一位
0/1: 开启或者关闭有一个锁监视器标志位
锁监视器标志位中指针指向Monitor 对象(锁监视器)的起始地址
每个对象都有一个Monitor 对象(锁监视器)与其关联
Monitor 对象(锁监视器)
Monitor 对象(锁监视器)中有一个指针,指向持有它的线程(_owner)
Monitor 对象还拥有两个队列, 一个是线程排队队列(_EntryList),一个是线程阻塞队列(_WaitList)。
当持有它的对象释放它的时候,指针会重制,指向等待队列中的下一个线程,
当线程执行 wait 时,它会进入阻塞队列_WaitList,唤醒后,进入排队队列_EntryList
synchronized 就是通过这种方法来实现锁的, 这也是为什么 Java 中任意对象都可以是锁的原因。
监视器跟对象共同的生命周期,但是在没有开启重量级锁之前,监视器是空对象,_owner 在开启重量级锁后才会指向线程
偏向锁、轻量级锁、自旋锁、锁消除
偏向锁
偏向锁就是,当一个线程获得了锁,就会进入偏向模式,当这个线程再次请求锁时,无需再做任何同步操作,即可获取锁(本质可以理解为单线程情况无锁)
(因为当只有一个线程反复操作锁的时候,不应该反复加锁, 这是额外的消耗,是需要尽量降低的)
当一个线程来获取锁,并不会第一时间修改锁状态位,而是先修改偏向锁的标志位,偏向锁默认为 1 即开启状态,还有一个线程标记,指向获取偏向锁的线程,线程要获取偏向锁,修改线程标记指向自己, 操作成功即得到锁,第二次再来访问锁时,比较当前的线程标记是否是自己,如果是,就直接访问. 但如果是第二个线程想要来获取锁,发现线程标记不是自己,就会锁升级到轻量级锁,升级后,偏向锁改为 0,即关闭偏向锁
但是对于锁竞争比较激烈的场合,偏向锁就失效了,当每次申请锁的线程都是不相同的,每个线程都获得了偏向锁,就没有了偏向锁的效果了
偏向锁失败后,会升级为轻量级锁
轻量级锁
当一个线程进入锁,有线程来争抢锁时,偏向锁升级为轻量级锁
来争抢的线程会进入自旋,即循坏等待锁被释放,即自旋锁
轻量级锁所适应的场景是线程交替执行同步块的场合
如果存在同一时间多个线程访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
重量级锁
重量级锁即 synchronized 实现的锁, 启用锁监视器 Monitor 对象
线程自旋次数增加后升级到重量级锁, 停止自旋, 监视器启动, 监视器中的_owner 属性指向当前占有锁的线程,
锁升级是单向的,即从低到高,从轻到重,锁的等级是通过对象中的锁信息里的状态位来标识,而多线程争抢锁,就会改变这个状态位
锁消除
锁消除即消除不必要的锁,或者缩小锁的范围