AQS-ReentrantLock入队出队源码解析

本文深入解析了Java中的ReentrantLock,包括其公平与非公平锁的概念。通过测试代码展示了ReentrantLock如何实现可重入、公平与非公平的特性,并详细分析了加锁和解锁过程,以及队列的入队和出队逻辑。文章还解释了公平锁遵循的先进先出原则以及在源码中的实现细节。
摘要由CSDN通过智能技术生成

下面是个人的理解与查看源码的备注,如果有不正确的地方,请评论指出。万分感谢

lock是可重入、 可公平与非公平的 、可共享与独占的

如何证明?请看下列测试代码

public class ReentrantLockTest {

    private static  int sum = 0;
    private static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(()->{
                //加锁 这里打断点并使用右键查看每个线程
                lock.lock();
                try {
                    for (int j = 0; j < 10000; j++) {
                        sum++;
                    }
                } finally {
                    // 释放锁
                    lock.unlock();
                }


            });
            thread.start();
        }

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(sum);
    }
}
  1. 第一步查看new ReentrantLock()
	// 默认非公平锁
 	public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
     // 通过传参可以设置是非公平锁还是公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

为什么说new FairSync()和new NonfairSync()就是代表公平锁和非公平锁?

什么是公平锁和非公平锁?
如果一个线程进来先插队直接一上来就获取锁,那么就是非公平锁。反之就是公平锁

在java\util\concurrent\locks\ReentrantLock.java

	// 非公平
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锁状态,
            if (compareAndSetState(0, 1))
            	// 修改成功,那么直接设置独占锁为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
            	// 失败,进入队列
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    // 公平
static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
        	// 直接进入队列
            acquire(1);
        }
  1. 调用lock方法后,查看如何入列的,查看acquire(1)方法?
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

!tryAcquire(arg)方法内部 -------这里可以说明是可重入锁

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

final boolean nonfairTryAcquire(int acquires) {
			// 当前线程
            final Thread current = Thread.currentThread();
            // 当前线程状态,如果是第一次进来这里c就等于1,1的状态是代表已经有锁了
            int c = getState();
            if (c == 0) {
            	// 这里是尝试获取锁,CAS修改state成功,设置锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // getExclusiveOwnerThread()这里是获取锁的对象
            // 如果当前对象和getExclusiveOwnerThread()相等,说明是同一个对象。那么state就会加1,说明这是可重入的
            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;
        }

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))重点方法重点方法重点方法
拆分成两个方法

	 addWaiter(Node.EXCLUSIVE)  添加任务到队列
	 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

addWaiter(Node.EXCLUSIVE)
一个重要属性Node,这里的队列也是使用双向链表,所以AQS内部维持了一个Node节点类

static final Node EXCLUSIVE = null;

Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
}

// 主要看这,其他的是调用的方法
private Node addWaiter(Node mode) {
		// mode是等于null的,可以自己去看下。这里new Node节点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        // tail是定义为队列的尾部,第一次进来肯定等于null
        Node pred = tail;
        if (pred != null) {
        	// 设置当前节点的父节点
            node.prev = pred;
            // 把之前的父节点改成当前节点,但是我debug看他的thread值并没有被改变!!! 有点奇怪
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // tail等于null走enq方法  重要方法重要方法 实现队列初始化
        // 说实话,可能java淘汰了我都写不出来这么经典的代码
        enq(node);
        return node;
    }

// 初始化队列   node等于当前线程的node
private Node enq(final Node node) {
		// node等于当前线程的节点,除了thread等于当前线程,其他属性都是默认值
        for (;;) {
        	// 第一次等于null,把尾节点复制给t
            Node t = tail;
            if (t == null) { // Must initialize
            	// 新建队列头和队列尾,然后再次循环
                if (compareAndSetHead(new Node()))
                	// 引用相等
                    tail = head;
            } else {
            	// 再次进来,设置当前节点的父节点等于t,可能有些人疑问,t不是尾节点吗,这里又是赋值父节点。
            	// 这是因为这个t就是一个空节点,也等于head
            	// 总结:设置新的尾节点的父节点等于上次的尾节点
                node.prev = t;
                // CAS修改尾节点tail等于当前节点,并不是t=node
                if (compareAndSetTail(t, node)) {
                	// 这时候的t和head是引用相等,t.next等于hread的next等于node
                	// 设置上个节点的next等于当前节点
                    t.next = node;
                    // 返回尾节点,这里有点容易混淆!!!
                    // 这里如果是第一次进来的,t可以说是父节点,也是尾节点。但第二次进来那么就是尾节点!!! **确定是尾节点**
                    return t;
                }
            }
        }
    }

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
阻塞队列,因为addWaiter只是形成链表

// node等于尾节点,arg等于1
final boolean acquireQueued(final Node node, int arg) {
		// 未知
        boolean failed = true;
        try {
        	// 设置中断标志
            boolean interrupted = false;
            // 又是一个自循环
            for (;;) {
            	// 获取node节点的父节点
                final Node p = node.predecessor();
                // 父节点是否等于队列头 tryAcquire这里再次尝试获取锁
                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);
        }
    }

// 我删掉了方法的英文注释,因为看不懂 
// 参数 尾节点的父节点和尾节点  node也可以说当前节点,因为当前节点就是尾节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		// 获取父节点的锁状态,非常之重要这个状态,第一次进来是0
        int ws = pred.waitStatus;
        // Node.SIGNAL表示这个线程是需要被阻塞的
        if (ws == Node.SIGNAL)
         
            return true;
        if (ws > 0) {
           
            do {
            // 这里没太明白,是说这里这个节点不需要了,这里删除节点
            // 大概是循环重组队列,删除
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
         	// 设置当前线程为Node.SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        // 退出,再次进来因为是自循环
        return false;
    }


 private final boolean parkAndCheckInterrupt() {
 		// 设置线程阻塞
        LockSupport.park(this);
        return Thread.interrupted();
    }


  1. ReentrantLock出队逻辑

lock.unLock()

// arg=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) {
			// state-1 状态等于0
            int c = getState() - releases;
            // 当前线程不等于独占锁里的线程报错
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                // 独占锁赋值为空
                setExclusiveOwnerThread(null);
            }
            // state赋值为0,识为可加锁
            setState(c);
            return free;
        }
        
private void unparkSuccessor(Node node) {
        // ws=-1
        int ws = node.waitStatus;
        if (ws < 0)
        	// 把waitStatus 改成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);
    }        

释放完锁之后又会进入到之前加锁那里

// node是线程1的node
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	// p=head
                final Node p = node.predecessor();
                // tryAcquire(arg)再这里获取了锁,必定成功,因为thread0已经释放了锁
                if (p == head && tryAcquire(arg)) {
                	// 设置head为当前node------------>其实队列头就是当前被锁住的线程
                    setHead(node);
                    // 清除之前的head节点与子节点的引用
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                	// 从这里出来  <----------------
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

private void setHead(Node node) {
        head = node;
        node.thread = null; // 这一步可以看到,其实head就是当前获取锁对象,只不过thread被清空了
        node.prev = null;
    }

总结一下: release方法是释放锁,释放完锁后还需要在acquireQueued方法内重组队列,lock是遵循先进先出原则
入队是可公平/可不公平的,但出队是公平的,先进先出

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值