ReentranLock部份源码:可以发现ReentrantLock本身并没有继承AQS,但是里面有个类叫做sync内部类继承了AQS;
ReentrantLock总体上做了几件事情:1.构造,2加锁(加锁失败要入队,入队之后要阻塞,阻塞之后需要等待被唤醒。)
第二个内部类:(NonfairSync)不公平的同步器
第三个内部类:公平的同步器
先看构造器:
1.无参在构造器,返回了一个不公平的同步器
2.有参构造,传入的是一个布尔量
3.这就是ReentrantLock既可以实现公平的锁,也可以实现不公平的锁的原因。
4.构造完成后调用lock方法上锁;
此处的方法具体是锁,主要是创建的对象是公平的锁还是非公平的锁决定的
5.acuire方法:(ctrl+单机acquire方法)
这段使用了短路原算法,如果尝试获得一个锁失败了,那么就会执行后面的内容,addwaiter.(Node.Exclusive)表示以独占的方式添加一个等待的线程。
ALT+CTRL点击tryAcquire进入非公平的锁,进入之后是这个函数:
这个函数又调用这个函数
获取当前的线程,获取状态,如果是0表示没有线程抢占锁,那么就使用cas抢占锁,成功之后就把自己设置独占的线程;就可以执行代码块。
否则就会判断
当前的线程是不是里面设置的正在运行的线程。判断的目的是表示线程重入,以便修改,所以就是自加了1;这就是非公平锁的工作过程;
总结就是非公平的同步锁上锁的过程就是: 调用lock()->acquire(1)->tryAcquire(1)&&acquireQueue(addWaiter(Node.Exclusive),arg)->
tryacquire(1):实现的原理是,先获取当前的线程,获取状态,若果是0就表示没有占有锁,于是使用cas的方式去抢占锁,抢占锁成功之后就设置当前的线程为一个为独占的线程;
否则判断当前的线程是不是正在运行的线程,判断的目的是表示线程重入;如果是就加1;
获得不了锁,先以排他锁的方式添加一个waiter,这各方法,先创建一个节点,这个节点是当前的一个线程;然后获取队列的队尾,如果这个队列的对尾不为空就将这个新的节点的前面指向队尾,然后使用cas原子性操作,将tail指向node,修改成功就把原先的旧尾巴的下一个指针指向node;
否则就说明这个队列是空的即tail == null 一旦tail == null就执行下面的initializeSyncQueue(),这个函数是一个初始化同步队列的函数:由于tail == null ,head == null ,他创建了一个空的节点,并且用cas操作将head换成了这个空节点,并且将tail指向这个空节点,这时tail就不再是null节点了,于是就可以执行之前的if的语句块,增加一个新的线程节点存放在waiter队列之中。
接着执行下面的操作:
之前的函数会返回一个节点,接着会执行下面的函数:acquireQueue()
这个函数帮助我们设置节点的阻塞状态,P节点为node的前一个节点,判断前一个节点是不是一个头,如果前一个节点是一个头结点,于是就再一次的请求锁(原因由于前一个节点是头,说明正在运行,就意味着可能很快就释放锁,于是尝试获取锁,如果前一个既是头结点并且强成功,那么就把自己设置为头结点,p节点(原先的节点)的next就指向null,这样做可以帮助Gc,这就是之前的addwaiter函数创建一个空节点的原因;并且空节点不会占用资源,所以接下来的节点一定会强锁成功,未来不一定能够强,但是只有一个线程抢锁); 挂起之前一直抢锁;如果抢不到就使用shouldparkAfterFailedAcquire(),让线程停。 。
做了以下的工作: 获得前一个节点的的等待状态,如果状态是singal就返回true;如果ws>0,就说明线程状态是cancelled,那么取消,于是就应该删除这个节点:P节点的ws>0时,就让node的前一个节点变为p节点的前一个,并且让p节点变为原先p的前一个,一直等到p节点的ws的值小于0,于是退出循环,就意味着p节点的状态是一个小于0的数字。并且让p的next指向node;中间的节点相当于被抛弃了;
初次之外还做了其他的判断,比如-2;就进入else语句:主要是将不是singal的变为singal;保证这个节点插入在一个singal节点之后;
接着执行函数
于是让线程处于等待状态。