阅读源码八字准则:化繁为简(直接看最终目的),由浅入深(再看怎样达到目的)。比如lock.lock()你首先要知道它最终就是改变了一个int值,然后再细究如何修改。
1. lock.lock()方法到底干了件什么事?
lock.lock()方法干的事可以用一句话形容:一顿操作猛如虎,定睛一看原地杵。这句话简直太合适了。我们都知道,加锁的时候用 lock.lock()方法,然后就锁住了。为什么就锁住了呢?其实就是干了一件很简单的事儿。就是修改了AQS中的一个int数值(这个就是方法的终极目的)。线程修改成功后就继续执行,否则就排队。但是多线程的时候为了保证每次只能一个线程修改成功,以及排队的线程有秩序执行,所以添加了很多的逻辑代码。就好比高中下课去吃饭,食堂只有一个窗口,很多人去抢,但每次只有一个人能打到饭,剩下的要排队。这是理想的状态,也是锁想要实现的状态。但是如何实现?就要添加逻辑。就比如你怎么保证学生会乖乖排队(谁不想插队呢),这时候的逻辑代码就好比安排老师值班,维护秩序,学生就乖乖排队了。总结:最终目的就是吃到饭,但是人多窗口少,所以要经过一系列程序才能吃到饭。虽然有点麻烦,但总比吃不到好啊。如果大家都去抢,把饭弄地上了,就都别吃了。然后就是怎么才能吃到饭的问题了。
2. Lock、ReentrantLock、Sync、NonfairSync、FairSync、AQS之间关系
lock.lock()方法要去修改的那个int值在AQS里面。AQS本来跟Lock没有任何关系的。但是经过标题2中的几个东西,他们就有关系了。关系如下:首先AQS是一个抽象类,它自己是独立的并且它里面有一个int值;然后Sync也是一个抽象类,它继承了AQS,所以通过它可以操作AQS里面int值;再然后,NonfairSync和FairSyncLock是Sync的子类,那它自然也是AQS的子类,也可以操作AQS里面的int,看名字知道一个是公平锁一个是非公平锁。这四个是一派,名为“纵”。其次,Lock是一个接口,ReentrantLock是Lock的实现类。这两个是一派,名为“横”。然后合纵连横,就达到了lock.lock()这句话的效果。如何联合呢?就是把先前的四个类作为ReentrantLock的属性设置到ReentrantLock类中(暂时这么理解),这样Lock就可以通过操作属性进而操作AQS里面的int值。这就是它们之间的逻辑关系。
3.lock.lock()方法调用流程
首先使用new ReentrantLock()创建Lock的对象,然后调用Lock的lock()方法,其内部调用的是Sync(Sync是Lock内部属性)子类(NonfairSync或者FairSync,看具体实现)的lock()方法,其内部又调用了其父类AQS的acquire(1)方法,其内部又调用了其子类(NonfairSync或者FairSync,看具体实现)的tryAcquire(1),至此,开始干正事,就是去修改AQS里面的一个int类型的state值。
4.lock.lock()是这么干的
首先AQS里面有一个int值,还有一个双向链表队列,里面的结点存放的是Thread类。意思就是大家排队去修改int值。然后线程进来后开始修改int值,使用的是CAS的方式(CAS不知道的建议百度一下,就是确保某时刻只有一个线程能修改成功,且效率高),如果某时刻只有一个线程来修改int值,那么就直接修改,不用排队,如果有多个线程修改,就会触发初始化队列的方法,初始化队列并进行排队,每次有新的结点过来时就加入队尾,使用死循环,确保一定可以加入队列,无论时间长短。然后每次排队时也会有一个小动作,就是每次入队后会检查一下自己前面的结点是否是头结点,如果不是的话就直接去排队;如果是的话就再用CAS去修改int值,修改失败就去排队,修改成功就会拿到锁。至此,lock.lock()方法结束。如果成功就继续执行,如果失败就原地杵。
5.总结
本文只是粗略的讲解了lock.lock()的关系和流程,只能算是化繁为简,其中还有很多的细节笔者目前也不是很清楚,还缺少由浅入深的过程。以笔者的功力,目前还不能完全解析源码,所以没有代码,望各位大佬海涵,如有错误,望各位大佬指正。