java锁

java锁

文章java里的锁总结的笔记

锁的分类

java中锁分为两种

  1. 一种为synchronized关键字,由jvm实现,是隐性的锁
  2. 另一种为Lock接口及其实现类,是显性的锁,需要手动获取释放

synchronized

使用

  1. 加在静态方法上,锁的是整个类
  2. 加在成员方法上,锁的是this
  3. synchronized(对象){},锁的是对象

java对象在内存中的布局

  1. 对象头:存放对象自身运行时的数据。有Hash码、分代年龄、重量级锁的指针、锁记录的指针等,对象头也叫Mark Word
  2. 实例数据:对象中字段的信息
  3. 对齐填充:对象起始地址得是8字节的整数倍,需要对齐填充

其中对象头里的重量级锁的指针指向的是一个Monitor对象(管程、监视器),和synchronized的实现有关联

大致原理

  1. synchronized修饰方法

调用方法时需要线程先拥有Monitor管程

Monitor只能由一个线程拥有

当方法执行完后出现异常时会自动释放Monitor

  1. synchronized用于同步代码块

通过synchronizedentersynchronizedexit指令来获取和释放Monitor

存在的问题

Monitor的实现需要依赖操作系统底层,所以需要进行用户态和内核态的转换,效率较低

解决问题的方式

通过添加各种锁来实现同步,而不是一上来就加重量级锁synchronized

锁的重量有低到高

偏向锁 -> 自旋锁 -> 轻量级锁 -> 重量级锁

  • 偏向锁:对象头的Mark Word为偏向锁结构,锁会偏向于第一次获取它的线程,当每次都是这个线程来获取锁时,不需要其他操作
  • 轻量级锁:当线程间发生竞争时,Mark Word会变为轻量级锁的结构,通过CAS(Compare And Swap)操作获取锁
  • 重量级锁:即synchronized

线程间竞争锁的激烈程度会导致锁升级,锁只能升级不能降级

按锁的性质分类

  1. 悲观锁:每次读写操作都加锁
  2. 乐观锁:每次写操作都通过CAS操作来尝试加锁
  3. 公平锁:按照FIFO来获取锁
  4. 非公平锁:抢占式的锁,synchronized属于非公平锁
  5. 独占锁:锁只能被一个线程持有
  6. 共享锁:锁可以被多个线程持有

Lock

Lock接口常见实现类

  • ReentrantLock,可重入锁
  • ReadLock,读锁
  • WriteLock,写锁

使用

以ReentrantLock为例

1
2
3
4
5
6
7
Lock lock = new ReentrantLock();
lock.lock(); // 加锁
try {
// 代码..
} finally {
lock.unlock(); // 释放锁
}
方法名称 描述
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

是基于ReentrantLockAbstractQueuedSynchronizer实现的

本身实现了ReadWriteLock接口

int state的前16位表示占有读锁的线程数、后16位表示占有写锁的线程的重入次数

1
2
3
4
5
6
7
8
9
static final int SHARED_SHIFT   = 16;

// 获取读锁数量
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }

static final int EXCLUSIVE_MASK = (1 << 16) - 1;

// 获取写锁数量
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

读读互容,写写互斥,读写互斥

获取读锁时,会判断是否有写锁,然后state的高16位+1表示加读锁

获取写锁时,会判断state是否为0,然后state置为1,每次重入state+1

释放读锁时,ThreadLocal保存着每个线程的重入次数,对应的重入次数减为0时state-1

释放写锁时,state-1,只有当state为0时才会释放


java锁
http://xwww12.github.io/2023/06/02/java/java锁/
作者
xw
发布于
2023年6月2日
许可协议