ReentrantLocak源码分析

ReentrantLock重入锁的源码分析

1.基本使用 创建锁对象

static Lock lock=new ReentrantLock();

// 构造方法传入true参数即为公平锁,后面会分析源码为什么加入true就为公平锁,及公平锁和非公平锁的区别在哪,什么是非公平锁,什么是公平锁。
static Lock fairLock=new ReentrantLock(true);

public static void inc(){
    lock.lock(); //获得锁(互斥锁) ThreadA 获得了锁
    try {
		// 执行业务逻辑  TODO  菜鸟张工
    } catch (InterruptedException e) {
        e.printStackTrace();
    }finally {
        lock.unlock();/
    }
}

2.分析源码的具体实现

2.1先来看下ReentrantLock的类图

[外链图片转在这里插入图片描述

2.1.1简述类图

ReentrantLock实现了Lock接口,Lock接口中定义了如下方法,

lock
lockInterruptibly
tryLock
tryLock
unlock
newCondition

Sync是ReentrantLock的内部类。Sync是同步器,继承了AbstractQueuedSynchronizer 也就是AQS,后面称为AQS。FairSync及NonfairSync 是ReentrantLock的内部类,这两个类继承了Sync。

上面基本使用例子中,lock.lock();会调用到Sync,sync再调用到 FairSync及NonfairSync 的lock方法中。

2.2 加锁源码分析

lock.lock()

Sync.java

    public void lock() {
        sync.lock();
    }

非公平锁类

    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            // 通过CAS来修改state的值为1,state=0的情况下才能修改成功。state是AQS里面标识锁的变量,通过volatitle来修饰,1代表共享资源被锁定,如果是同一个线程重入的state会累加。
            if (compareAndSetState(0, 1))
                // 设置AQS中的exclusiveOwnerThread 为当前线程,这个变量用来记录是哪一个线程获取到了锁,后续重入的时候用这个变量值和当前进入的线程相比较,如果相等那么就是重入的了。
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // state没有修改成功,也就是锁被其他线程抢走了
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

AQS类

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 当前线程没有获取到锁
            selfInterrupt();
    	}

// tryAcquire是再去尝试获取一次锁,这个tryAcquire是只有非公平模式下才有的,如果tryAcquire返回了true就直接返回了,反到调用方。

ReentrantLock.java

// 非公平模式去获取锁    
protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

ReentrantLock.java

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            // 获取打线程
            final Thread current = Thread.currentThread();
            // 获取AQS的锁的状态 就是state
            int c = getState();
            // 如果值为0代表着刚刚获取到锁的线程已经释放了锁,所以这时候当前线程可以获得锁的了。
            if (c == 0) {
                // 修改AQS 的stats状态为acquires 也就是1
                if (compareAndSetState(0, acquires)) {
                    // 设置AQS 的exclusiveOwnerThread为当前线程 也就是标识下锁是被哪个线程持有了 
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 这里就是重入了 如果持有锁的线程和当前线程一致就是重入了。
            else if (current == getExclusiveOwnerThread()) {
                // AQS 的state加1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                
                // 设置AQS的status
                setState(nextc);
                return true;
            }
            return false;
        }

假设nonfairTryAcquire返回的是false,也就是当前线程没有获取到锁,应该执行if条件内的语句了。

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

先看addWaiter(Node.EXCLUSIVE)

Node.EXCLUSIVE是一个node为null的节点,

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
    // 当前线程放入到node中
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    
    // 这是第一个线程进来tail是null 也就是pred为null。
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 所以把上面的node加入到enq
    enq(node);
    return node;
}

/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
private Node enq(final Node node) {
    for (;;) {
        // t为null
        Node t = tail;
        if (t == null) { // Must initialize
            // head = tail = null 头 尾节点都为null 开始下一次循环
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 开始构建双端链表 链表的首节点是null
            node.prev = t;
            // 首节点tail节点之乡当前线程节点
            if (compareAndSetTail(t, node)) {
                // 首节点next节点指向当前线程节点
                t.next = node;
                return t;
            }
        }
    }
}
// 如果后面再有线程来获取锁并且假如持有锁的线程一直没有释放,都会依次的加入到这个双端链表中来

链表构建结束,然后acquireQueued开始执行这个方法

/**
 * Acquires in exclusive uninterruptible mode for thread already in
 * queue. Used by condition wait methods as well as acquire.
 *
 * @param node the node
 * @param arg the acquire argument
 * @return {@code true} if interrupted while waiting
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 当前想要获取锁的线程node  获取他的前一个节点 如果前一个节点为head 开始再次尝试获取锁
            // 也就是head节点的next节点会尝试2次去获取锁,构建双端链表之前一次(这次如果在非公平模式下,每个线程来获取锁的时候都会有这次)  然后这里的tryAcquire是第二次  就是head下的第一个节点多尝试一次去获取锁。
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                // 获取到了锁然后当前节点设置为head 他的前一个节点设置为null 然后他的前一个节点就会被gc了
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            
            // 如果当前线程的前一个节点不是head 那么开始把当前线程挂起 释放cpu  
            // p 是当前线程的前一个节点  node是当前想要获取锁的节点
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

线程挂起前把node对应的waitStatus打上singal标识

/**
 * Checks and updates status for a node that failed to acquire.
 * Returns true if thread should block. This is the main signal
 * control in all acquire loops.  Requires that pred == node.prev.
 *
 * @param pred node's predecessor holding status
 * @param node the node
 * @return {@code true} if thread should block
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // AQS的标识waitStatus 用来标识node的线程是等待还是挂起了的
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL) // siganl 已经挂起了直接返回true
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) { // 大于0 说明等待锁期间线程中断了
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 修改成singal
    }
    return false;
}

线程挂起

/**
 * Convenience method to park and then check if interrupted
 *
 * @return {@code true} if interrupted
 */
private final boolean parkAndCheckInterrupt() {
    // 走的是Unsafe.park 底层来挂起当前线程
    LockSupport.park(this);  // 这时候当前线程就柱塞在这里了  1
    //1.直到持有锁的线程释放了,然后依据双端链表的head的next节点找到当前线程进行一个 LockSupport.unpark  然后在接着这句代码下面继续走
    // 2.当前线程在阻塞期间 业务代码内执行了线程中断,Thread.interrupted  这句代码接着开始执行 。这句说的不对  链表中等待锁的线程主动interrupted 后 被柱塞的线程也不会主动唤醒  而是必须要等到第一种情况发生才可以。链表中的node节点等待锁的过程中有么有发生中断的判定是通过 Thread.interrupted来断定的。
    return Thread.interrupted();
    
    // 上面return这句代码会调用return currentThread().isInterrupted(true);  如果放生了中断那么返回的是true
}

上面方法中线程已经被唤醒了 ,所以接着下面的方法继续走。

  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())   
                    
                    // parkAndCheckInterrupt()如果线程是被中断了 那么这个方法返回的就是true ,如果是持有锁的线程释放了锁,轮到了这个线程那么 parkAndCheckInterrupt他的返回值就是false
                    interrupted = true; // 中断标识位
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

接着这句代码继续走

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 如果被中断了 那么执行seltInterrrupt方法 中断当前线程
        selfInterrupt();
}

2.3 释放锁的源码分析

ReentrantLock.java

/**
 * Attempts to release this lock.
 *
 * <p>If the current thread is the holder of this lock then the hold
 * count is decremented.  If the hold count is now zero then the lock
 * is released.  If the current thread is not the holder of this
 * lock then {@link IllegalMonitorStateException} is thrown.
 *
 * @throws IllegalMonitorStateException if the current thread does not
 *         hold this lock
 */
public void unlock() {
    sync.release(1);
}

AbstractQueuedSynchronizer.java AQS

/**
 * Releases in exclusive mode.  Implemented by unblocking one or
 * more threads if {@link #tryRelease} returns true.
 * This method can be used to implement method {@link Lock#unlock}.
 *
 * @param arg the release argument.  This value is conveyed to
 *        {@link #tryRelease} but is otherwise uninterpreted and
 *        can represent anything you like.
 * @return the value returned from {@link #tryRelease}
 */
public final boolean release(int arg) {
    if (tryRelease(arg)) {// 这个方法就不深入说了 就是吧AQS的state规制为0 ExclusiveOwnerThread 为null
        // 头节点不为null 并且waitStatus为singal  也就是-1  小于0 开始唤醒头节点的下一个节点
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
/**
 * Wakes up node's successor, if one exists.
 *
 * @param node the node
 */
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    // 设置为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    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);
}

2.4图解加锁及释放锁的流程

在这里插入图片描述

2.5公平锁非公平锁解释

上面源码已经阐述了公平和非公平大致是怎么走的,这里在简单说下。强不到锁的线程会被加入到双端链表中去,假如这时候持有锁的线程释放了锁,然后这个释放锁之后还没有等到这个线程去唤醒链表中的节点的时候,就又来了一个线程C,那么线程C上来是不会排对的,就是不会加入到双端链表中的尾部节点中去的,而是上来就尝试tryAcquire去获取锁,因为这时候的AQS的state为0,所以会被线程C设置为1.那么线程C就获取到了锁。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值