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方法。
标签:Node,AQS,lock,ReentrantLock,介绍,state,线程,公平 From: https://www.cnblogs.com/zhang12345/p/17541976.html