ReentrantLock源码分析全

目录

 

图片分析过程

源码分析

一、t1线程拿锁源码分析过程

二、t2线程拿锁失败进入队列阻塞源码分析过程

三、t3线程拿锁失败进入队列阻塞源码分析过程

四、t1释放锁源码分析过程

五、t2线程被唤醒后拿锁源码分析过程

六、t2线程释放锁源码分析过程

七、t3线程被唤醒后拿到锁源码分析过程

八、t3线程释放锁源码分析过程


下面的源码分析将围绕三个线程的使用来介绍源码,先图解,后源码解释,这里只讲公平锁!!!

借助代码分析源码

import java.util.concurrent.locks.ReentrantLock;

/**
 * 三个线程进来,阻塞两个,进行ReentrantLock源码分析
 * @author xuexue
 *
 */
public class Test {
	//创建ReentrantLock对象,公平锁
	private static ReentrantLock reentrantLock = new ReentrantLock(true);
	public static void main(String[] args) throws InterruptedException {
		//创建线程t1
		Thread t1 = new Thread("t1"){
			public void run() {
				//给t1加锁
				reentrantLock.lock();
				try {
					//打印线程名
					System.out.println(getName());
					//让线程t1睡眠,让线程t2、t3进入阻塞,进入队列
					sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} finally {//释放t1锁
					reentrantLock.unlock();
				}
			}
		};
		
		//创建线程t2
		Thread t2 = new Thread("t2"){
			public void run() {
				//给t2加锁
				reentrantLock.lock();
				try {
					//打印线程名
					System.out.println(getName());
				} finally {//释放t2锁
					reentrantLock.unlock();
				}
			}
		};
		//创建线程t3
		Thread t3 = new Thread("t3"){
			public void run() {
				//给t3加锁
				//reentrantLock.lock();
				try {
					//打印线程名
					System.out.println(getName());
				} finally {//释放t3锁
					//reentrantLock.unlock();
				}
			}
		};
		
		//分别启动三个线程
		t1.start();
		t2.start();
		t3.start();
		
	}

}

 

图片分析过程

1、当t1进来拿到锁之后,紧接着t2上来拿锁,进行初始化队列

2、队列初始化成功,线程t2入队

3、t3入队

4、t1释放锁后,t2拿到锁出队

5、t2释放锁后,t3拿到锁出队

源码分析

一、t1线程拿锁源码分析过程

1、进入lock()方法

2、继续sync进入lock()方法-----(sync是Sync类,继承了AQS的内部类)

3、进入acquire(1)方法-----参数1是cas尝试拿锁操作用的

4、进入tryAcquire方法-----t1是第一个线程,拿锁成功,方法结束,返回ture

5、全部返回,代码继续向下执行,执行代码输出t1

但是遇到sleep(2000)睡眠2s,这里故意设置睡眠,从而达到让t1/t2/t3线程存在竞争,让t2/t3进入队列并阻塞

 

二、t2线程拿锁失败进入队列阻塞源码分析过程

7、因为t1休眠,此时cpu分配给了t2(或者t3这里假设分配给了t2)

8、继续1、2、3步操作,继续分析

此时state=1,拿锁失败,并且t2不是锁持有线程t1,所以返回false

9、执行入队操作,进入方法addWaiter() 参数Node.EXCLUSIVE=null是一个Node节点

10、进入addWaiter 方法-----创建线程t2的节点,获取队尾节点,队尾节点为null,所以进入end()方法

11、进入end()方法-----初始化队列过程

第一次循环,tail=null,new Node() 创建了一个新的节点cas操作,将head设置为这个新节点,并将tail也设置为这个节点

第二次循环,将t2线程的Node节点前驱指向head,进行cas操作,将t2线程的Node设置为tail 就是tail=node,将head.next=node 链表关系建立好,队列就是维护链表的关系

aqs为什么不一开始初始化队列?因为如果是单线程或者线程交替执行的时候就不需要用到队列,当有竞争的时候才需要

此过程对应的图解(但是ws还没设值,在13步设值)

12、将t2线程的Node节点返回,进入acquireQueued()方法-----

拿到t2 node的前驱head,进入判断,进入自旋,t1未释放锁,自旋拿锁失败

第一次自旋拿锁失败进入shouldParkAfterFailedAcquire(参数1:head,参数2:t2线程的Node节点)

下次还会自旋一次,这里设置自旋,为了尽量不让线程park进入阻塞,尽量在java级别实现,不调用os的api,提高效率

13、进入shouldParkAfterFailedAcquire(p, node)方法----

perd.waitStatus就是head.waitStatus,初始化值为0,2线程 直接进入 cas操作 将head的waitStatus置为-1

返回false,结束方法,进入第二次循环,第二次自旋

每次入队都会将上一个Node的waitStatus置为-1状态,目的也是为了多自旋一次

为什么阻塞状态ws=-1要让下一个队列线程来修改?(入队的时候会把上一个线程状态ws改为-1)

1、因为线程阻塞park了,自己不能修改ws状态,不能之前修改,怕出异常了

2、睡眠了是让队列下一个线程看到的,自己看不看无关紧要

14、进行第二次自旋,目的还是为了尽量不让线程park进入阻塞,尽量在java级别实现,不调用os的api,提高效率

15、第二次进入shouldParkAfterFailedAcquire(p, node)-----在上一次head.waitStatus已经被设置为-1,直接返回true

16、进入parkAndCheckInterrupt()-----阻塞park队列中的t2线程,在这个位置阻塞,下次唤醒也是这个位置,记住了

方法不能正常返回结束,代码被阻塞在这里,无法继续往下执行,等待唤醒

 

三、t3线程拿锁失败进入队列阻塞源码分析过程

17、t2被阻塞后,因为t1休眠(休眠时间够长),此时cpu分配给了t3,线程t3此时进来

18、继续1、2、3步骤往下执行

此时state=1,拿锁失败,并且t3不是锁持有线程t1,所以返回false

19、进入addWaiter 方法-----创建线程t3的节点,获取队尾节点,队尾节点为t2线程的Node

线程t3入队操作,维护队列关系

此过程对应的图解(但是ws还没设置)

20、将t2线程的Node节点返回,进入acquireQueued()方法-----

拿到t3线程Node的前驱t2线程的Node,不会自旋拿锁,直接进入shouldParkAfterFailedAcquire(参数1:t2线程的Node,参数2:t3线程的Node节点)

下次还会自旋一次,这里设置自旋,为了尽量不让线程park进入阻塞,尽量在java级别实现,不调用os的api,提高效率

21、进入shouldParkAfterFailedAcquire()方法------

pred.waitStatus就是t2线程Node的状态,就是0,第一次进入时,将队列上一个元素的waitStatus置为-1,返回false

22、第二次进入shouldParkAfterFailedAcquire()-----在上一次t2.waitStatus已经被设置为-1,直接返回true

23、进入parkAndCheckInterrupt()-----阻塞park队列中的t2线程,在这个位置阻塞,下次唤醒也是这个位置,记住了

方法不能正常返回结束,代码被阻塞在这里,无法继续往下执行,等待唤醒

四、t1释放锁源码分析过程

24、线程t2、t3都已经被阻塞,等待t1释放锁被唤醒,此时t1释放锁

25、进入unlock()方法-----

26、进入release()方法-----尝试释放锁

27、进入tryRelease(arg)方法-----

t1线程只上了一次lock(),所以state为1,c=state-1=0,就是释放锁,并将持有锁线程置为空,将state设置为0,返回true

(如果多次加锁,需要多次释放锁才可以完全释放

28、返回true,进入if 语句体,拿到head节点,调用unparkSuccessor() 唤醒头节点Node的下一个元素

29、进入unparkSuccessor()-----

将head的waitStatus置为0,唤醒head下一个Node的线程,这里就是t2线程,方法结束

30、一直返回,直到finnally结束,进入唤醒线程位置,就是当时被阻塞的位置

五、t2线程被唤醒后拿锁源码分析过程

31、t2线程被唤醒,进入t2当时被阻塞的位置,继续执行代码,代码返回false

32、继续for循环一次,拿锁成功,返回false,方法层层返回

对应的图解过程

33、方法正常返回,输出t2线程名字,继续执行,释放t2线程锁

六、t2线程释放锁源码分析过程

34、进入unlock()方法,release()方法-----

35、进入unparkSuccessor()方法-----唤醒t3线程,结束方法,层层返回

36、结束t2线程,进入t3被阻塞的位置继续执行

七、t3线程被唤醒后拿到锁源码分析过程

37、t3线程被唤醒,进入t3当时被阻塞的位置,继续执行代码,代码返回false

38、继续for循环一次,拿锁成功,返回false,方法层层返回

对应的图解过程

39、方法正常返回,输出t3线程名字,程序继续执行,释放t3锁

八、t3线程释放锁源码分析过程

40、进入unlock()方法,release()方法----- 因为此时队列没有线程的Node,head的waitStatus=0,返回false,结束方法,层层返回

此时的head和tail的状态

41、t3释放锁成功,三个线程执行完毕,程序结束

42、输出结果(t1线程立即输出,t2、t3隔两秒输出。也有可能t3在t2前面执行,上面源码分析假设t2在t3前面执行)

猜你喜欢

转载自blog.csdn.net/qq_41055045/article/details/100600867