1.程序调用lock()方法来进入lock的流程
public class TestLock {
private int count = 0;
public static void main(String[] args) {
TestLock t = new TestLock();
Lock lock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
t.count++;
System.out.println(t.count);
lock.unlock();
}
}).start();
}
}
}
先设置一个简单的上锁方法运行看看结果:
发现能够正确输出,说明上锁有效。
2.debug方式解析源码
先总体概括一下:
-
调用ReentrantLock类的lock()方法
public void lock() { sync.lock(); }
发现lock()方法内部调用的了sync.lock(),这个sync为ReentrantLock的内部类
-
Sync的lock()方法:
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
这个lock方法是Sync的一个具体的方法,为ReentrantLock另一个内部类NonfairSync的一个方法,这个方法就开始真正的开始做上锁的操作了
我们一句一句的看这个代码的:
- compareAndSetState(0, 1) ,这个代表用CAS的操作去设置一下AQS的内部的一个state属性,如果成功,则就执行
setExclusiveOwnerThread(Thread.currentThread())这个是将当前线程设置为独占,也就是相当于获取到了当前这把锁 acquire(1)这个是调用了AQS内部的方法
AQS实现acquire()
acquire()具体代码如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//tryAcquire是尝试去获取锁,如果获取到了就返回,如果获取失败了则调用线程中断的方法来中断当前线程
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//这句代码分成了两步,先执行addWaiter()方法来将当前线程放置到AQS的一个等待队列的末尾,acquireQueueed()方法是将等待线程的头结点去获取锁,如果获取到了锁,则将当前的线程放到队列的头结点中,去等待获取锁
selfInterrupt();
}
-
tryAcquire(arg) 掉用的是Sync的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState();//获取到当前state的值 if (c == 0) { // 如果state的值为0,则代表当前的锁没有线程占用,则进去用CAS的方式直接占用锁,并返回true,表示当前线程占用到了这把锁 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { //如果state不是等于0,则代表当前锁已经被占用了,需要判断占用的锁的线程是不是当前线程,如果是当前线程的话则直接进入,并将state的值加一,代表重入了一次,这也是重入锁的表现 int nextc = c + acquires; //state的值加一 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc);//设置state的值 return true; } return false; }
注:state是AQS的核心,这个变量是voaltile修饰的,代表锁的重入次数
-
addWaiter()方法 由于AQS的内部有一个等待队列,队列的每个节点就是Node对象private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); //将当前线程封装成Node对象 // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { //如果队列不为空的话就用CAS的方式设置新建的Node对象为尾结点, node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node);//如果队列为空,则将Node节点插入到队列中 return node; }
注:Node也是AQS重要的组成部分
-
acquireQueued()这个方法的作用就是调用队列中的节点去获取锁final boolean acquireQueued(final Node node, int arg) { //这个node为我们刚刚新建的当前线程的节点,是队列中的尾结点 boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); //for循环的方式来走到队列的头结点来 if (p == head && tryAcquire(arg)) { //再用CAS去获取一遍锁 setHead(node);//当前这个节点设置为头结点,更好的争抢锁,所以为非公平锁 p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) //获取锁失败之后要将该线程设置为中断的状态,避免浪费CPU的资源 interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
总结
ReentrantLock 的lock方法的内部源码实现是使用了AQS的实现,而我们也通过这一遍的debug的方式看到了AQS的内部频繁的使用了CAS这个自旋的操作,由于是无锁,所以效率非常的高。我们也知道ReentrantLock是一个可重入锁,具体我们也看到了其的内部实现,就是使用了AQS的state这个属性,只要是当前获取到锁的线程再重新进来的时候,我们的state值就加一,代表重入的次数增加了一次,由于state是volatile修饰的属性,所以他可以保证在多线程下的可见性。
我们还看到了AQS中一个非常重要的属性 Node ,AQS内部获取锁的时候有一个等待队列,队列中的每个节点都是Node对象,所以我们可以知道AQS 的核心就是voaltile修饰的state和node对象