ReentrantLock的介绍

Java中提供了两种锁:synchronized和lock锁,ReentrantLock属于lock锁,是互斥锁,可以让多线程执行期间只有一个线程在执行指定一段代码。

二、ReentrantLock的lock方法:

2.1简单分析

进入lock方法后,发现内部调用了sync.lock()方法,去找方法的实现,发现了两个实现。

Fairsync 公平锁,每个线程都会在执行lock方法时,查看是否有线程排队,如果有,直接去排队,如果没有才会去尝试竞争一个锁资源。

NonfairSync 非公平锁,每个线程都会在执行lock方法时,先尝试获取所资源,获取不到再排队。

非公平锁的效率更高,在new Reentrantlock时候传入参数true。

公平锁直接使用无参构造方法。

从源码的角度也发现了公平锁直接调用acquire方法尝试获取锁,而非公平锁会先基于CAS方式尝试获取锁资源,如果获取不到才会执行acquire方法。

2.2分析AQS

AQS就是AbstractQueuedSynchronizer类,AQS内部维护着一个队列,有三个核心属性state head tail

 

2.3lock()方法源码

 1 //非公平锁的lock方法
 2 final void lock() {
 3 //    使用CAS方式,将state从0改为1
 4     if (compareAndSetState(0, 1))
 5 //        证明state修改成功,也就代表获取锁资源成功
 6 //        将当前线程设置到AQS的exclusiveOwnerThread(AOS中),代表当前线程拿着锁资源
 7         setExclusiveOwnerThread(Thread.currentThread());
 8     else
 9         acquire(1);
10 }
11 //公平锁的lock方法
12 final void lock() {
13     acquire(1); }

 

 三、acquire()方法

//不管是公平锁还是非公平锁,都会调用acquire方法
public final void acquire(int arg) {
//    tryAcquire方法分为两种实现,第一种是公平锁,第二种是非公平锁
//    公平锁操作:如果state为0,再看是否有线程排队,如果有就去排队.如果state不为0,看是否是自己拿着锁资源,是否可以做一个锁重入的操作,如果可以直接获取锁
//    非公平锁:如果state为0,直接尝试CAS修改.如果state不为0,如果是锁重入的操作,直接获取锁
    if (!tryAcquire(arg) &&
//            addWaiter在线程没有通过tryAcquire拿到锁资源时,需要将当前线程封装为Node对象,去AQS排队
//            acquireQueued方法查看当前线程是否排在队伍前面的,如果是尝试获取锁资源,如果长时间没拿到锁,也需要将当前线程挂起
            acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
        selfInterrupt();
}

 

 

 四、tryAcquire()方法

是AQS提供的,内部并没有任何的实现,需要继承AQS的类自己去实现逻辑代码,查看到tryAcquire在ReentantLock中提供了两种实现:公平锁和非公平锁

非公平锁

//非公平锁的实现方式
final boolean nonfairTryAcquire(int acquires) {
//    获取当前线程
    final Thread current = Thread.currentThread();
//    获取AQS的state
    int c = getState();
//    如果state为0,代表当前没有线程占用锁资源
    if (c == 0) {
//        直接基于CAS的方式,尝试修改state,从0修改为1,如果成功就代表拿到锁资源
        if (compareAndSetState(0, acquires)) {
//            将exclusiveOwnerThread属性设置为当前线程
            setExclusiveOwnerThread(current);
//            返回true
            return true;
        }
    }
//    说明state肯定不为0,不为0就代表当前lock被线程占用
//    判断占用锁资源的线程是不是当前线程
    else if (current == getExclusiveOwnerThread()) {
//        锁重入操作,直接对state加1
        int nextc = c + acquires;
//        判断锁重入是否达到最大值
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
//        设置AQS中的state
        setState(nextc);
        return true;
    }
    
    return false;
}

 

 公平锁

//公平锁实现
protected final boolean tryAcquire(int acquires) {
//    获取当前线程
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
//        没有线程占用锁资源,首先查看有没有线程排队
        if (!hasQueuedPredecessors() &&
//                如果没有线程排队,尝试CAS获取锁资源
                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;
}
}

 

 公平锁和非公平锁的tryAcquire方法唯一区别就是,当判断state为0之后

  • 公平锁会 先查看是否有线程在排队,如果有,直接返回false,如果没有线程排队,执行CAS尝试获取锁资源
  • 非公平锁不管有没有线程排队,直接以CAS的方式尝试获取锁资源

五、addWaiter()方法

在线程执行tryAcquire方法没有获取锁资源之后,会返回false,再配置上if中的!操作,会执行&&后面的方法,而在

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))的参数中执行了addWaiter()方法,要将当前获取锁失败的线程封装为Node,排队到AQS的队列中。
整体逻辑为先初始化Node节点,将当前线程传入,并且标识为互斥锁。
尝试将当前Node插入到AQS队列的末尾,
  • 队列为空,执行enq,先初始化空Node作为头,再将当前Node插入,作为tail
  • 队列不为空,直接将当前Node插入,作为tail
//获取锁失败 排队到AQS的队列中
private Node addWaiter(Node mode) {
//    将线程封装为Node对象
    Node node = new Node(Thread.currentThread(), mode);
    // 获取到tail节点
    Node pred = tail;
//    如果tail节点不为null,表示正在有人排队
    if (pred != null) {
//        将当前节点的prev指向tail
        node.prev = pred;
//        避免并发问题,以CAS方式将tail指向当前线程
        if (compareAndSetTail(pred, node)) {
//            将之前的tail的next指向当前节点
            pred.next = node;
//            返回当前节点
            return node;
        }
    }
//    如果在队列为空或者CAS操作失败后,执行enq方法将当前线程排队队列末尾
    enq(node);
    return node;
}

 

六、ReentantLock的acquireQueued方法

首先查看当前node是否排在队列的第一个位置(不算head),直接再次执行tryAcquire方法竞争锁资源,尝试将当前线程挂起,最终排在有效节点后才会将当前线程挂起。

七、ReentantLock的unlock方法

unlock释放锁操作不分为公平和非公平,都是执行sync的release方法

释放锁的核心就是将state从大于0的数值改成0即为释放锁成功,unlock应该会涉及到AQS队列中阻塞的线程进行唤醒,阻塞的方法是park方法,唤醒必然是unpark方法。

 

 

 

 

posted on 2023-07-10 18:29  啥123  阅读(22)  评论(0编辑  收藏  举报