Java 锁 实现原理总结

背景

总结一下 java 里面的锁

  • Synchronized
  • ReentrantLock
  • LockSupport

synchronized

Java中的每一个对象都可以作为锁

  • 同步普通方法,锁是当前实例对象。
  • 同步静态方法,锁是当前类的Class对象。
  • 同步方法块,锁是Synchonized括号里配置的对象。

实现原理

  • 同步方法, JVM 采用 ACC_SYNCHRONIZED 标记符来实现同步
    方法级的同步是隐式的。同步方法的常量池中会有一个 ACC_SYNCHRONIZED 标志。
    当某个线程要访问某个方法的时候,会检查是否有 ACC_SYNCHRONIZED,
    如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。
    这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。
    值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

  • 同步代码块, JVM采用 monitorenter、monitorexit 两个指令来实现同步
    可以把执行 monitorenter 指令理解为加锁,执行 monitorexit 理解为释放锁。
    每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0
    当一个线程获得锁(执行 monitorenter )后,该计数器自增变为 1,当同一个线程再次获得该对象的锁的时候,计数器再次自增。
    当同一个线程释放锁(执行 monitorexit 指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。

  • 锁的信息维护在Java头对象里面, Java 对象头包括

    • Mark Word
    • 指向类的指针
    • 数组长度(只有数组对象才有)

    这里只重点说一下 Mark Word, Mark Word 记录了对象和锁有关的信息,当这个对象被 synchronized 关键字当成同步锁时,围绕这个锁的一系列操作都和 Mark Word 有关

    java-markword

锁优化

每一个线程在准备获取共享资源时:

  1. 检查 MarkWord 里面是不是放的自己的 ThreadId, 如果是, 表示当前线程是处于 “偏向锁”
  2. 如果 MarkWord 不是自己的 ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据 MarkWord 里面现有的 ThreadId,通知之前线程暂停,之前线程将 Markword 的内容置为空
  3. 两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord
  4. 第三步中成功执行CAS的获得资源,失败的则进入自旋
  5. 自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败
  6. 进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己

ReentrantLock

1
2
3
4
5
6
7
8
9
private final ReentrantLock lock = new ReentrantLock();

public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}

实现原理

  • new ReentrantLock() 初始化
    • 公平锁
    • 非公平锁

基于 AQS AbstractOwnableSynchronizer

  • lock.lock() 仅包含非公平锁的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    final void lock() {
    if (compareAndSetState(0, 1))
    setExclusiveOwnerThread(Thread.currentThread());
    else
    acquire(1);
    }
    public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
    }
    final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    if (compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(current);
    return true;
    }
    }
    else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0) // overflow
    throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
    }
    return false;
    }
    final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
    boolean interrupted = false;
    for (;;) {
    final Node p = node.predecessor();
    if (p == head && tryAcquire(arg)) {
    setHead(node);
    p.next = null; // help GC
    failed = false;
    return interrupted;
    }
    if (shouldParkAfterFailedAcquire(p, node) &&
    parkAndCheckInterrupt())
    interrupted = true;
    }
    } finally {
    if (failed)
    cancelAcquire(node);
    }
    }
    static void selfInterrupt() {
    Thread.currentThread().interrupt();
    }
    1. 如果之前无线程获取锁,则标志当前线程获取独占锁, 否则,进入 acquire(1)
    2. tryAquire 的逻辑为 获取 state 值(父类中 volatile 修饰的整型属性)
      • 如果 state = 0,则标志当前线程获取独占锁
      • 如果 state != 0, 并且获得独占锁的线程是当前线程,则 state 值+1(这也是可重入的原因)
        以上条件都返回 true,拿到了锁,可继续往下执行
    3. 如果没拿到独占锁,则将当前线程包装到 Node 里面,acquireQueued 加入等待队列(Node组成的链表)
      并且不断的用 Node 链表的尾节点 tryAcquire 尝试获取锁
  • lock.unlock()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public void unlock() {
    sync.release(1);
    }
    public final boolean release(int arg) {
    if (tryRelease(arg)) {
    Node h = head;
    if (h != null && h.waitStatus != 0)
    unparkSuccessor(h);
    return true;
    }
    return false;
    }
    protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
    free = true;
    setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
    }

    unlock 释放锁 tryRelease 对 state 值(父类中 volatile 修饰的整型属性)进行自减操作(重入机制会导致 state 值 > 1)
    直到 state = 0 锁完全释放

LockSupport

通过 Unsafe 类里的函数实现的锁

实现原理

1
2
3
4
5
6
7
8
9
10
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}

所有线程共享一个 permit

  • park 拿到 permit,获得锁继续执行,其他线程只能阻塞, 因为不支持重入,一个线程多次 park 会一直阻塞下去
  • unpark 释放 permit,释放锁,可唤醒其他线程继续执行