ReentrantLock加锁解锁剖析

本文详细剖析了Java并发库中的ReentrantLock实现,主要依赖于AQS(AbstractQueuedSynchronizer)。AQS维护了一个双向链表队列,用于管理等待锁的线程。ReentrantLock的加锁与解锁过程围绕state属性进行,支持公平锁和非公平锁。非公平锁在尝试获取锁时更为激进,而公平锁会检查是否有线程在等待。解锁时,通过AQS的release方法唤醒后续等待线程。
摘要由CSDN通过智能技术生成

ReentrantLock原理剖析

说到ReentrantLock,我们都知道他是基于AQS(AbstractQueuedSynchronizer)的可重入互斥锁。

我们想要剖析ReentrantLock,就不得不先从AbstractQueuedSynchronizer开始。

剖析AbstractQueuedSynchronizer

AbstractQueuedSynchronizer有一个内部类Node,底层维护了一个双向链表而形成一个队列。也就是我们的AQS队列。当我们获取锁失败后的线程会被放到这个队列中,等待其他线程释放lock的时候被唤醒。

ReentrantLock整个加锁过程就是围绕着state属性来进行的。当state字段大于0时,代表当前资源被锁住,并且state的值代表当前持有锁线程的重入次数,当state为0时,队列中的线程就可以通过一系列的CAS操作来争抢这个锁。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    
    static final class Node {
		//等待状态
        volatile int waitStatus;

		//前一个结点
        volatile Node prev;

        //后一个节点
        volatile Node next;

        //当前节点线程
        volatile Thread thread;
        
        //头指针
        private transient volatile Node head;

       	//尾指针
        private transient volatile Node tail;

        //重入次数
        private volatile int state;

        }
   }

Sync

Sync是ReentrantLock的抽象内部类,继承了AQS,ReentrantLock的主要功能全靠这个内部类Sync。他定义了获取锁和释放锁的通用模板。并且定义了非公平获取锁方法。

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;
    
    //非公平锁获取方法
    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;
        }
}

Sync有两个亲儿子,NoFairSync(非公平锁)和FairSync(公平锁),ReentrantLock的主要功能就是通过这两个实现类来具体实现。

NoFairSync(非公平锁)

获取锁流程
  1. 尝试使用CAS加锁
    final void lock() {
        //尝试使用CAS加锁
        if (compareAndSetState(0, 1))
            //加锁成功,将当前资源锁线程设置为当前线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
        //加锁失败,走父类Sync定义的加锁流程
            acquire(1);
    }
    
  2. 若CAS加锁失败,走Sync定义的加锁流程
    public final void acquire(int arg) {
    	//尝试加锁
        if (!tryAcquire(arg) &&
        	//加锁失败,将当前线程放到AQS队列尾部
        	//并判断当前线程是不是排在AQS队列第一个,若是,重新尝试获取锁,若不是,则找到前一个可用线程,并挂起当前线程
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
  3. tryAcquire非公平加锁,使用的是父类Sync定义好的nonfairTryAcquire()方法
    protected final boolean tryAcquire(int acquires) {
    	//父类Sync定义好的nonfairTryAcquire()
        return nonfairTryAcquire(acquires);
    }
    
    protected final boolean nonfairTryAcquire(int acquires) {
        	//获取当前线程
            final Thread current = Thread.currentThread();
            //获取state值
            int c = getState();
            //如果state为0,代表可以上锁
            if (c == 0) {
            	//使用CAS方式将state从0设置为1
                if (compareAndSetState(0, acquires)) {
                    //若CAS加锁成功,则将持有锁资源线程设置为当前线程
                    setExclusiveOwnerThread(current);
                    //返回加锁成功
                    return true;
                }
            }
            //如果加锁失败,判断持有锁资源线程是不是当前线程
            //如果是,则开始锁重入操作
            //如果不是,则放弃重入
            else if (current == getExclusiveOwnerThread()) {
                //state + 1
                int nextc = c + acquires;
                //超出重入次数限制,抛出异常
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                //设置state = state + 1
                setState(nextc);
                //返回加锁成功
                return true;
            }
            //否则,返回加锁失败
            return false;
        }
    
  4. addWaiter(Node.EXCLUSIVE) 若获取锁失败,将当前线程添加到AQS队尾
    //加锁失败,将当前线程加到AQS队列尾部
        private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        //获取AQS队尾节点
        Node pred = tail;
        if (pred != null) {
            //将当前线程的前一个节点指向原来的尾部
            node.prev = pred;
            //使用CAS方式将原来的尾节点的指针指向刚刚这个新的线程
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //若使用CAS将当前线程设置到AQS尾部失败,则执行此方法
        enq(node);
        return node;
    }
    
    //使用死循环,直到将当前线程设置到队尾成功为止,才跳出循环
    private Node enq(final Node node) {
        for (;;) {
            //获取队尾指针
            Node t = tail;
            //若队尾指针为null,则证明AQS是一个空队列,需要被初始化
            if (t == null) {
                //创建一个node并设置为头指针
                if (compareAndSetHead(new Node()))
                    //队尾指针指向头指针
                    tail = head;
            } else {//若队尾指针不为null,将当前线程设置到队尾
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
    
  5. acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 判断当前线程是否排在AQS前面,若是,则重新尝试获取锁,若不是,则找到AQS前面的等待线程,将自己挂起
    //判断当前线程是不是排在AQS队列的第一个
    //若是,则重新获取锁
    //若不是,则将当前线程挂起
    final boolean acquireQueued(final Node node, int arg) {
        //获取锁失败flag
        boolean failed = true;
        try {
            //中断flag
            boolean interrupted = false;
            //死循环:线程若是AQS前面的节点,则可以去尝试获取锁,若不是,则从后往前直到找到一个活跃的线程节点,然后将自己挂起
            for (;;) {
                //获取当前线程的前一个节点
                final Node p = node.predecessor();
                //若当前线程的前一个节点是head,则证明当前线程在AQS头部,重新尝试获取锁
                if (p == head && tryAcquire(arg)) {
                    //获取锁成功,下一步操作...
                    //将当前线程设置为head节点
                    setHead(node);
                    //释放对象方便垃圾回收
                    p.next = null; // help 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;
        	//若前一个线程也是处于等待状态,则返回true,可以将当前线程挂起
            if (ws == Node.SIGNAL)
                return true;
        	//如果前一个线程是取消状态,那么久一直向前遍历,直到获取到状态小于0的线程
            if (ws > 0) {
                do {
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {
                //将找到的这个线程状态设置为等待(-1)
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            return false;
        }
    	
    	//调用park()方法将线程挂起
        private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }
    

FairSync(公平锁)

公平锁和非公平锁的区别:
  1. 非公平锁刚进入lock方法就会使用CAS尝试获取锁,获取锁失败才会调用acquire()方法,而公平锁是直接调用acquire()方法

    //非公平锁
    final void lock() {
        //默认先使用一次CAS
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    
    //公平锁
    final void lock() {
        	//直接调用获取锁通用方法
            acquire(1);
    }
    
  2. 非公平锁在acquire()获取锁方法中没有判断AQS队列是否存在排队现象,不管当前线程是否存在AQS队列中都可以去争抢锁。而公平锁会先调用hasQueuedPredecessors()方法查看队列是否存在排队,若有排队现象,会将当前线程存入AQS队列尾部,然后才去尝试获取锁

    //非公平锁
    final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                   	//获取锁方法中没有判断AQS队列是否存在排队现象,不管当前线程是否存在AQS队列中都可以去争抢锁
                    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;
            }       
    
    //公平锁
    protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                	//先调用hasQueuedPredecessors()方法查看队列是否存在排队
                    if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0)
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
        }
    

解锁过程 unlock()

公平锁和非公平锁解锁都是走的一样的代码,都是使用AQS的release()方法进行解锁。

public void unlock() {
    sync.release(1);
}
release()过程
public final boolean release(int arg) {
    //尝试解锁
    if (tryRelease(arg)) {
        Node h = head;
        //解锁成功,若头节点的状态 !=0 说明AQS还存在排队线程 需要将后面的线程唤醒
        if (h != null && h.waitStatus != 0)
            //从后往前找到离头节点最近的线程进行唤醒,准备抢锁
            unparkSuccessor(h);
        return true;
    }
    return false;
}
  1. 释放锁 tryRelease()

    //尝试释放锁
    protected final boolean tryRelease(int releases) {
        //state - 1
        int c = getState() - releases;
        //若当前线程 != 持有锁线程  抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        //释放资源成功标记falg
        boolean free = false;
        //c == 0 代表state == 0 代表释放资源成功
        if (c == 0) {
            free = true;
            //持有锁线程设置为null
            setExclusiveOwnerThread(null);
        }
        //state = state - 1 等待下次重入-1
        setState(c);
        return free;
    }
    
  2. 释放锁成功 唤醒AQS中的其他等待线程

    private void unparkSuccessor(Node node) {
        //获取头节点的状态
        int ws = node.waitStatus;
        //如果头节点状态 > 0 那么将头节点的状态设置为0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
    	//获取头节点的下一个节点
        Node s = node.next;
        //找到离头节点最近的线程进行唤醒操作
        //如果下一个节点的状态 > 0 说明下一个线程已经放弃获取锁
        if (s == null || s.waitStatus > 0) {
            s = null;
            //从AQS尾部从后往前找,找到离找到离头节点最近的线程
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //进行唤醒操作
        if (s != null)
            LockSupport.unpark(s.thread);
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值