一、什么是锁

锁是一种同步机制,能够在多个线程访问资源时进行限制,避免造成数据的不一致。
锁需要底层设备支持,能够实现一种或者多种原子操作,比如 test andadd fetch and put compare and swap。

二、锁的相关概念

1、锁开销

锁占用的资源、初始化锁、获得、释放锁的时间

2、锁竞争

一个线程获取另一个线程持有锁的情况

3、死锁

存在两个线程,双方都有对方持有的锁,都在等待对方释放锁。

锁的粒度越粗,所包括的数据量越大,锁的开销越小,但是锁的竞争越大;
锁的粒度越细,锁的个数变多,锁的竞争越小,锁的开销越大。

4、独享锁/共享锁

独享锁:锁只能被单个线程占用,比如ReentrantLock Synchronized
共享锁:锁可以被多个线程占用,比如 ReadWriteLock

5、互斥锁/读写锁

互斥锁与读写锁是独占锁/共享锁的具体实现,ReentrantLock是互斥锁,ReadWriteLock是读写锁,都是通过AQS来实现的
锁升级:读锁到写锁(不支持)
锁降级:写锁到读锁(支持)

ReentrantReadWriteLock
写锁占据低16位,读锁占据高16位

6、公平锁/非公平锁

公平锁:按照线程申请锁的顺序分配锁
非公平锁:锁的分配不是按照线程申请锁的顺序,可能后申请的线程能够先拿到锁,可能造成饥饿现象。
ReentrantLock默认是非公平锁,但是可以通过构造函数创建公平锁,非公平锁的吞吐量比公平锁大。
Synchronized也是非公平锁,它不能生成公平锁。

7、可重入锁

也叫递归锁,当线程的外层获取了锁,内层方法自动获取锁
ReentrantLock与Synchronized都是可重入锁,一定程度上避免了死锁。
ReentrantLock获取锁的过程
当线程查询到当前对象的状态为0时,表示没有线程占用锁,就会将执行AQS操作,将stat加1,同时设置对象的持有线程ID为当前线程,返回true;
如果对象的线程ID等于当前对象,则会将stat加1,返回true。
以上都不是时,则没有获取锁,线程阻塞。
当释放锁的时候,会将stat-1,如果stat不等于0,则还持有锁,直到为0时,当前线程才释放锁,返回true。

8、乐观锁/悲观锁

这是一种看待并发的角度
乐观锁:认为并发操作都不是更新操作,不需要加锁
悲观锁:认为并发操作都是更新操作,如果不加锁,则会出现问题。

9、自旋锁

如果线程没有获取到锁,线程不会阻塞,会采用循环的方式尝试获取锁,会一直占用CPU资源。

10、偏向锁/轻量级锁/重量级锁

对象头的mark word
mark word在不同状态时标志位存储
无锁态:前25位位对象的hashcode,4位分代年龄,是否偏向锁为0,锁标志位为01,
轻量级锁:指向栈中锁记录的指针(30位)锁标志位为00
重量级锁:指向互斥量(重量级锁)的指针,锁标志位为10
GC标记:空,锁标志位为11
偏向锁:线程ID(23位),epoch(2位),分代年龄(4位),是否偏向锁(1位)为1,锁标志位为(2位)01
锁共有四种状态,级别从低到高分别是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。随着竞争情况锁状态逐渐升级、锁可以升级但不能降级。

11、轻量级加锁

1、在进入同步代码块的时候,如果同步对象锁状态为无锁状态,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(lock record)的空间,用于存储锁对象目前的额mark word的拷贝,称为dispatched mark word
2、拷贝对象头中mark word到锁记录中;
3、拷贝成功后,jvm使用CAS操作尝试将对象的mark word更新为指向lock record的指针,并将lock record里的owner指针指向object mark word,如果执行成功,则执行步骤4,否则执行步骤5
4、更新成功后,那么当前线程就拥有了该对象的锁,并且对象mark word的锁标志位设置为00,表示此对象 处于轻量级锁标志。
5、如果更新失败,则说明多个线程竞争锁,轻量级锁膨胀为重量级锁。

12、重量级加锁

轻量级锁向重量级锁膨胀过程中,一个操作系统的互斥量(mutex)和条件变量(condition variable)会和这个被锁对象关联起来。
在锁膨胀时,被锁对象的markword会被通过CAS操作尝试更新为一个数据结构的指针,这个数据结构中进一步包含了指向操作系统互斥量和条件标量的指针。