java锁
java锁
文章java里的锁总结的笔记
锁的分类
java中锁分为两种
- 一种为
synchronized
关键字,由jvm实现,是隐性的锁 - 另一种为
Lock
接口及其实现类,是显性的锁,需要手动获取释放
synchronized
使用
- 加在静态方法上,锁的是整个类
- 加在成员方法上,锁的是
this
synchronized(对象){}
,锁的是对象
java对象在内存中的布局
- 对象头:存放对象自身运行时的数据。有Hash码、分代年龄、重量级锁的指针、锁记录的指针等,对象头也叫
Mark Word
- 实例数据:对象中字段的信息
- 对齐填充:对象起始地址得是
8字节的整数倍
,需要对齐填充
其中对象头里的重量级锁的指针指向的是一个Monitor对象(管程、监视器),和synchronized
的实现有关联
大致原理
- 当
synchronized
修饰方法
调用方法时需要线程先拥有Monitor管程
Monitor只能由一个线程拥有
当方法执行完后出现异常时会自动释放Monitor
- 当
synchronized
用于同步代码块
通过synchronizedenter
和synchronizedexit
指令来获取和释放Monitor
存在的问题
Monitor的实现需要依赖操作系统底层,所以需要进行用户态和内核态的转换,效率较低
解决问题的方式
通过添加各种锁来实现同步,而不是一上来就加重量级锁
synchronized
锁的重量有低到高
偏向锁 -> 自旋锁 -> 轻量级锁 -> 重量级锁
- 偏向锁:对象头的
Mark Word
为偏向锁结构,锁会偏向于第一次获取它的线程,当每次都是这个线程来获取锁时,不需要其他操作 - 轻量级锁:当线程间发生竞争时,
Mark Word
会变为轻量级锁的结构,通过CAS(Compare And Swap)操作获取锁 - 重量级锁:即
synchronized
锁
线程间竞争锁的激烈程度会导致锁升级,锁只能升级不能降级
按锁的性质分类
- 悲观锁:每次读写操作都加锁
- 乐观锁:每次写操作都通过
CAS
操作来尝试加锁 - 公平锁:按照FIFO来获取锁
- 非公平锁:抢占式的锁,
synchronized
属于非公平锁 - 独占锁:锁只能被一个线程持有
- 共享锁:锁可以被多个线程持有
Lock
Lock接口常见实现类
- ReentrantLock,可重入锁
- ReadLock,读锁
- WriteLock,写锁
使用
以ReentrantLock为例
1 |
|
方法名称 | 描述 |
---|---|
void lock() | 获取锁 |
boolean tryLock() | 尝试获取锁,没获取到锁不会阻塞 |
void unlock() | 释放锁 |
Condition newCondition() | 创建等待队列,调用wait()方法可以加入等待队列 |
AbstractQueuedSynchronizer
同步器AQS,是实现锁的基础;
主要方法
获取/设置同步状态
方法 | 描述 |
---|---|
int getState() | 获取同步状态 |
void setState() | 设置同步状态 |
boolean compareAndSetState(int expect, int update) | CAS设置同步状态 |
获取独占锁/共享锁,可以覆盖重写
方法 | 描述 |
---|---|
boolean tryAcquire(int arg) | 独占式获取同步状态 |
boolean tryRelease(int arg) | 独占式释放同步状态 |
int tryAcquireShared(int arg) | 共享式获取同步状态 |
boolean tryReleaseShared(int arg) | 共享式私房同步状态 |
boolean isHeldExclusively() | 检测当前线程是否获取独占锁 |
模板方法
方法 | 说明 |
---|---|
void acquire(int arg) | 独占式获取同步状态,该方法将会调用 tryAcquire 尝试获取同步状态。获取成功则返回,获取失败,线程进入同步队列等待。 |
void acquireInterruptibly(int arg) | 响应中断版的 acquire |
boolean tryAcquireNanos(int arg,long nanos) | 超时+响应中断版的 acquire |
void acquireShared(int arg) | 共享式获取同步状态,同一时刻可能会有多个线程获得同步状态。比如读写锁的读锁就是就是调用这个方法获取同步状态的。 |
void acquireSharedInterruptibly(int arg) | 响应中断版的 acquireShared |
boolean tryAcquireSharedNanos(int arg,long nanos) | 超时+响应中断版的 acquireShared |
boolean release(int arg) | 独占式释放同步状态 |
boolean releaseShared(int arg) | 共享式释放同步状态 |
通过FIFO队列给来获取锁的线程排队
节点分为独占式和共享式
- 独占式: 首节点获取同步状态,当释放同步状态后,会唤醒队列的下一个节点
- 共享式:首节点获取同步状态,若下一个节点也是共享节点,则会唤醒下一个节点
ReentrantLock
可重入锁,支持公平非公平,默认非公平
通过Unsafe类的pack()
、unpack()
、compareAndSwap..()
、来实现线程的阻塞、唤醒和CAS操作
内部类sync实现AbstractQueuedSynchronizer接口,通过sync实现公平锁、非公平锁
获取锁:
- lock()方法
- 通过CAS操作尝试加锁
- 成功则设置自己为当前锁的持有者
- 失败则调用
acquire()
,会再尝试一次,再失败则会阻塞(加入到AQS的等待队列)
释放锁:
- unlock()方法
- 释放锁后通过AQS的等待队列唤醒下一个线程,唤醒的线程回去争抢锁
可重入:
- 当前线程每次获取锁时state++
- 释放锁时只有state == 0才会释放锁
ReentrantReadWriteLock
是基于ReentrantLock
和AbstractQueuedSynchronizer
实现的
本身实现了ReadWriteLock
接口
int state的前16位表示占有读锁的线程数、后16位表示占有写锁的线程的重入次数
1 |
|
读读互容,写写互斥,读写互斥
获取读锁时,会判断是否有写锁,然后state的高16位+1表示加读锁
获取写锁时,会判断state是否为0,然后state置为1,每次重入state+1
释放读锁时,ThreadLocal
保存着每个线程的重入次数,对应的重入次数减为0时state-1
释放写锁时,state-1,只有当state为0时才会释放