转载

再探JAVA重入锁

再探JAVA重入锁

之前的文章中简单的为大家介绍了重入锁 JAVA并发之多线程基础(2) 。这里面也是简单的为大家介绍了重入锁的几种性质,这里我们就去探索下里面是如何实现的。

我们知道在使用的时候,必须锁先有定义,然后我们再拿着当前的锁进行加锁操作,然后处理业务,最后是释放锁的操作(这里就拿里面非公平锁的实现来讲解)。

字节码操作

public class com.montos.lock.ReentrantLockDemo implements java.lang.Runnable {
  public static java.util.concurrent.locks.ReentrantLock lock;

  public static int k;

  public com.montos.lock.ReentrantLockDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void run();
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: sipush        1000
       6: if_icmpge     29                  //int类型的值进行栈顶比较
       9: getstatic     #2                  // Field lock:Ljava/util/concurrent/locks/ReentrantLock;
      12: invokevirtual #3                  // Method java/util/concurrent/locks/ReentrantLock.lock:()V
      15: getstatic     #4                  // Field k:I
      18: iconst_1
      19: iadd                              
      20: putstatic     #4                  // Field k:I
      23: iinc          1, 1
      26: goto          2
      29: iconst_0
      30: istore_1
      31: iload_1
      32: sipush        1000
      35: if_icmpge     50
      38: getstatic     #2                  // Field lock:Ljava/util/concurrent/locks/ReentrantLock;
      41: invokevirtual #5                  // Method java/util/concurrent/locks/ReentrantLock.unlock:()V
      44: iinc          1, 1
      47: goto          31
      50: return

  public static void main(java.lang.String[]) throws java.lang.InterruptedException;
    Code:
       0: new           #6                  // class com/montos/lock/ReentrantLockDemo
       3: dup
       4: invokespecial #7                  // Method "<init>":()V
       7: astore_1
       8: new           #8                  // class java/lang/Thread
      11: dup
      12: aload_1
      13: invokespecial #9                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      16: astore_2
      17: new           #8                  // class java/lang/Thread
      20: dup
      21: aload_1
      22: invokespecial #9                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      25: astore_3
      26: aload_2
      27: invokevirtual #10                 // Method java/lang/Thread.start:()V
      30: aload_3
      31: invokevirtual #10                 // Method java/lang/Thread.start:()V
      34: aload_2
      35: invokevirtual #11                 // Method java/lang/Thread.join:()V
      38: aload_3
      39: invokevirtual #11                 // Method java/lang/Thread.join:()V
      42: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
      45: getstatic     #4                  // Field k:I
      48: invokevirtual #13                 // Method java/io/PrintStream.println:(I)V
      51: return

  static {};
    Code:
       0: new           #14                 // class java/util/concurrent/locks/ReentrantLock
       3: dup
       4: invokespecial #15                 // Method java/util/concurrent/locks/ReentrantLock."<init>":()V
       7: putstatic     #2                  // Field lock:Ljava/util/concurrent/locks/ReentrantLock;
      10: iconst_0
      11: putstatic     #4                  // Field k:I
      14: return
}
复制代码

这里面无非就是入栈,栈元素比较,出栈放入变量中这些操作,没有之前的 synchronized 里面的监视器相关指令限制,只是简单的一些栈操作。

加锁操作

final void lock() {
            if (compareAndSetState(0, 1)) //将同步状态从0变成1 采用cas进行更新
                setExclusiveOwnerThread(Thread.currentThread());//设置当前拥有独占访问权的线程。
            else
                acquire(1);//没有获取到锁,则进行尝试操作
        }
复制代码

往下面的选择走:

public final void acquire(int arg) {
        //先进行再次尝试获取锁的操作,如果获取失败则将当前加入队列中,并设置中断标志。
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
复制代码

首先走尝试获取锁的操作(这里还是走非公平锁的):

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;
        }
复制代码

接着往下走:

private Node addWaiter(Node mode) {
        //独占模式进行封装当前线程
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        if (pred != null) {如果尾节点不为null,将当前的节点接入并返回
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
复制代码

继续往下走:

private Node enq(final Node node) {
        for (;;) {//
            Node t = tail;
            if (t == null) { // 初始化尾节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {//尾节点与当前的节点互换
                    t.next = node;
                    return t;//返回当前节点
                }
            }
        }
    }
复制代码

接着回去往下走:

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; // 手动清除引用  帮助GC
                    failed = false;
                    return interrupted;
                }
                //检测获取锁失败的节点状态  以及暂时挂起并返回当前的中断标志
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);//取消正在进行的获取尝试。
        }
    }
复制代码

说真的,咱们直接看失败的情况,我们接着往下走:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //检查和更新无法获取的节点的状态。
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //该节点已经设置了请求释放信号状态,所以可以进行安全挂起
            return true;
        if (ws > 0) {
            do {//清除不需要执行的节点
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //waitstatus必须为0或传播。表明我们需要信号,但不要挂起。调用者重试以确保在挂起前无法获取。
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
复制代码

然后看向下一个方法:

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//挂起当前线程
        return Thread.interrupted();//返回中断标识
    }
复制代码

上面的取消获取队列里面的节点就不看了.. cancelAcquire(node) ,里面就是取消正在进行的获取尝试。同时将无需的节点移除。当上面的操作走完之后就设置当前线程中断标识。这里面主要流程是说如果加锁不成功之后,对于当前线程是怎么执行操作的,我们可以看到,里面的方法中大部分在获取不到锁之后,下一步操作中会再次尝试获取下,如果获取不到才会继续执行,获取到了我们就可以直接使用,这里也是多线程操作里面的魅力, 每一个空隙中就可能会让当前线程进行获得锁的操作

释放锁操作

释放锁的步骤就简单许多了:

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;//只有当前重入次数为0,才能返回true
                setExclusiveOwnerThread(null);//当前独占线程设为NULL
            }
            setState(c);//重新设置同步状态
            return free;
        }
复制代码

然后往下走:

private void unparkSuccessor(Node node) {
        //当前状态为负数,则尝试清除当前的线程状态
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        //清除取消或无效的节点,从尾部向后移动以找到实际节点
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);//释放当前线程
    }
复制代码

从上面的顺序往下面来看,我们主要发现线程在拿锁阶段是有许多的操作的,要根据线程的状态再将线程从等待队列中移除。释放的时候就显得简洁了许多,我们只需要看到当前线程的状态-1,然后看看是否是重入的。

我们通过一个简单的重入锁代码可以看到,作者在用无锁的操作去获得锁,这个整体的步骤里面考虑的东西很多,每一个时刻,线程都有可能千变万化,我们需要了解的是我们每一个步骤都需要可能发生的情况。如果能够考虑到发生的情况,那么有些步骤就可以直接跳过,我们直接就可以获得最后的结果(这块在线程尝试获锁的阶段可以体现)。有小伙伴对于重入锁还有什么看法的可以在下面进行留言,我们可以相互学习,共同进步~

原文  https://juejin.im/post/5d11be136fb9a07f0b03d054
正文到此结束
Loading...