AQS源码解析(jdk1.8)

目录

一、AQS是什么:

二、实战:

1、tryLock单线程上锁和解锁的代码(未重入,非公平锁)

2、tryLock多线程加锁释放锁代码(未重入,非公平锁)

3、tryLock线程重入代码(重入,非公平锁)

4、lock.lock()单线程上锁和解锁的代码(未重入,非公平锁)

5、lock.lock()多线程上锁和解锁的代码(未重入,非公平锁)

6、lock.lock()线程重入代码(重入,非公平锁)

7、lock.lock()公平锁


一、AQS是什么:

AQS,即AbstractQueuedSynchronizer, 队列同步器,它是Java并发用来构建锁和其他同步组件的基础框架。

它维护了一个volatile int state(代表共享资源)和一个FIFO(双向队列)线程等待队列

二、实战:

1、tryLock单线程上锁和解锁的代码(未重入,非公平锁)

package com.zhangmen.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

	private static Lock lock = new ReentrantLock();

	public static void main(String[] args) {
		lock.tryLock();
		try {
			doSomeThing();
		} catch (Exception e) {
			// ignored
		} finally {
			lock.unlock();
		}
	}

	private static void doSomeThing() {
		System.out.println("做点什么");
	}
}

当程序执行到lock.tryLock()时,断点进入java.util.concurrent.locks.ReentrantLock.tryLock()方法:

我们可以看到,tryLock默认的是非公平锁,传入值为1,再跟进去:

我们发现调用的是ReentrantLock的内部类的nonfairTryAcquire方法,那么从代码的值,第一个线程进来getState=0,然后执行了compareAndSetState方法,将state通过CAS改为了1,成功后,将当前线程设置成所得持有者线程,完毕后返回true,是不是很简单;

当程序执行到lock.unlock()时,我们再来看看AQS都干了些什么,断点进入到java.util.concurrent.locks.ReentrantLock.unlock(),

接着往下走,进入到AQS的方法:java.util.concurrent.locks.AbstractQueuedSynchronizer.release(int),因为Sync继承了AQS,所以进入到了AQS类中,

先来看看tryRelease方法,回调到了java.util.concurrent.locks.ReentrantLock.Sync.tryRelease(int)中:

这个时候,getState()是为1的,releases为传入的值1,上面加锁的时候也将当前线程设置为了锁持有者线程,那么就会走到if(c==0)条件里面,再将所持有者线程设置为null,出来,将state设置为0,返回true,是不是 so easy!!!

为什么在加锁state变为1的时候需要用到CAS而释放锁的时候不需要呢?因为这个时候多个线程都在修改state,用CAS的目的是为了在多线程并发的情况下只有一个线程能够修改状态成功。

返回true之后,回到 if (tryRelease(arg))判断中,进入条件,将h设置为null,直接返回true,释放锁代码结束!!

2、tryLock多线程加锁释放锁代码(未重入,非公平锁)

package com.zhangmen.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

	private static Lock lock = new ReentrantLock();

	public static void main(String[] args) {
		// 多线程调用
		new Thread(() -> {
			doSomeThing();
		}, "thread-1").start();
		System.out.println("thread-1已经执行完毕,下面主线程开始执行......");
		// 主线程调用
		mainThreadTest();

	}

	private static void mainThreadTest() {
		doSomeThing();
	}

	private static void doSomeThing() {
		try {
			if (lock.tryLock()) {
				System.out.println("做点什么");
				Thread.sleep(1000000000);
			}
		} catch (Exception e) {
			// ignored
		} finally {
			lock.unlock();
		}
	}
}

这样当thread-1调用doSomething方法时,就会被一直休眠,当main线程tryLock时,进入到:

此时c=1,判断current == getExclusiveOwnerThread()为false,所以直接返回false,抢锁失败,然后就会执行finally方法,直接报错:

因为main线程执行释放锁逻辑时,需要判断:

当前线程不是持有锁的线程,所以抛异常了,然后我们得改改代码:

只有锁住的线程才会去释放锁,其他线程不能操作

3、tryLock线程重入代码(重入,非公平锁)

可重入锁加锁几次就得释放几次

package com.zhangmen.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

	private static Lock lock = new ReentrantLock();

	public static void main(String[] args) {
		// // 多线程调用
		// new Thread(() -> {
		// doSomeThing();
		// }, "thread-1").start();
		// System.out.println("thread-1已经执行完毕,下面主线程开始执行......");
		// 主线程调用
		mainThreadTest();

	}

	private static void mainThreadTest() {
		doSomeThing();
	}

	private static void doSomeThing() {
		if (lock.tryLock()) {
			try {
				System.out.println("第一次获取锁......");
				if (lock.tryLock()) {
					try {
						System.out.println("第二次获取锁......");
					} catch (Exception e) {
						// ignored
					} finally {
						lock.unlock();
					}
				}
			} catch (Exception e) {
				// ignored
			} finally {
				lock.unlock();
			}
		}
	}
}

main线程第一次tryLock的如情况1,我们来分析第二次tryLock的情况

这个时候,发现c=1了,结果就执行下面的判断,current == getExclusiveOwnerThread(),发现当前线程确实是所持有者线程,nextc=1+1,通过setState(nextc),将state=2,返回true,然后进入第一段释放锁的代码

此时,c=2-1,并且将state设置为1,返回false,main线程执行第二段释放所的代码,如情况1,好啦,非公平锁我们已经讲完了,大家有没有收获~~~~

4、lock.lock()单线程上锁和解锁的代码(未重入,非公平锁)

new ReentrantLock()默认为非公平锁

package com.zhangmen.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

	private static Lock lock = new ReentrantLock();

	public static void main(String[] args) {
		// // 多线程调用
		// new Thread(() -> {
		// doSomeThing();
		// }, "thread-1").start();
		// System.out.println("thread-1已经执行完毕,下面主线程开始执行......");
		// 主线程调用
		mainThreadTest();

	}

	private static void mainThreadTest() {
		doSomeThing();
	}

	private static void doSomeThing() {
		lock.lock();
		try {
			System.out.println("第一次获取锁......");
		} catch (Exception e) {
			// ignored
		} finally {
			lock.unlock();
		}
	}
}

进入lock方法,就会进入方法:java.util.concurrent.locks.ReentrantLock.NonfairSync.lock()

我们看到,aqs就是把state改为了1,并且设置锁持有者线程为当前线程,接着走到释放锁代码中,如情况1所示

5、lock.lock()多线程上锁和解锁的代码(未重入,非公平锁)


package com.zhangmen.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

	private static Lock lock = new ReentrantLock();

	public static void main(String[] args) {
		// 多线程调用
		new Thread(() -> {
			doSomeThing();
		}, "thread-1").start();
		System.out.println("thread-1已经执行完毕,下面主线程开始执行......");
		// 主线程调用
		mainThreadTest();

	}

	private static void mainThreadTest() {
		doSomeThing();
	}

	private static void doSomeThing() {
		lock.lock();
		try {
			System.out.println("做点什么......");
			Thread.sleep(100000000);
		} catch (Exception e) {
			// ignored
		} finally {
			lock.unlock();
		}
	}
}

进入lock方法java.util.concurrent.locks.ReentrantLock.NonfairSync.lock():

lock.lock()是没有返回值的,根据情况4所示,线程thread-1抢到锁之后就会将state的状态改为1,当main线程进入之后就抢不到锁,就会走进入else,走acquire这个方法java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(int):

我们先来看看tryAcquire这个方法

再进去,发现是不是很熟悉:

这不就是我们上面tryLock讲到的方法嘛,哈哈哈,但是我们还是再来看一遍,main线程进入之后,c=1,判断current == getExclusiveOwnerThread(),为false,直接返回false,那么回到acquire这个方法:

!tryAcquire(arg)就是true,接下来看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,但是首先得进入addWaiter方法java.util.concurrent.locks.AbstractQueuedSynchronizer.addWaiter(Node):

我们会发现,new了一个Node(其实这就是AQS维护的FIFO队列,是一个双向列表,这个类里面有volatile Node prev,volatile Node next两个变量),这个Node是AQS的内部类,构造函数里面给两个两边赋了值:

只是nextWaiter外部传入为null,我们接着往下看,pred为null,然后进入enq方法java.util.concurrent.locks.AbstractQueuedSynchronizer.enq(Node):

这是一个死循环,进入,第一次循环,t=null,进入compareAndSetHead方法,将AQS的head节点设置为new Node(),并且将head节点赋值给tail节点,也就是说现在AQS的head节点和tail节点都指向同一个地址,现在开始第二次循环,这时候tail是有值的,接着走,将addWaiter中实例的节点Node node = new Node(Thread.currentThread(), mode)的pre节点设置成t,然后进入compareAndSetTail方法,将AQS的tail节点设置成这个node节点地址,成功之后再将t的next属性设置成node节点地址,也就是将head节点的next属性设置成node节点地址,然后返回t,跳出循环,哈哈哈,是不是很晕,我们来梳理一遍哈:

最终返回的t,也就是tail节点哈,他们指向的是同一引用,返回之后,addWaiter方法也返回此node节点,我们再进入acquireQueued方法java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(Node, int):

又是一个死循环,哈哈,我们再来分析分析,第一次进入循环,执行predecessor方法,这个方法就是返回了node的pre节点信息,那么p == head肯定为true,进入tryAcquire方法,因为thread-1已经占了锁,所以main线程肯定抢锁失败,那么就会走shouldParkAfterFailedAcquire条件判断,执行方法java.util.concurrent.locks.AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire(Node, Node):

pred.waitStatus默认为0,就会走compareAndSetWaitStatus方法,将前一个节点也就是head节点的waitStatus的值通过CAS设置成-1,返回false,,当前节点的waitStatus还是0,接着走第二次循环,此时再次进入shouldParkAfterFailedAcquire方法,上次循环我们已经将waitStatus设置成了-1,那么就会走if (ws == Node.SIGNAL)条件语句,然后return true,接着走parkAndCheckInterrupt方法java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt():

阻塞,并将interrupted设置为true,那我们现在来思考一个问题,如果此时main线程一直在处于lock的代码逻辑里面,还没有走到parkAndCheckInterrupt这个方法里面进行阻塞,第三个线程来抢锁了,那么我们从头再来看,首先会进入到lock方法里面:

compareAndSetState肯定失败,所以会走acquire(1)方法,接着:

tryAcquire方法抢锁肯定会失败,那么肯定会走addWaiter方法:

这个时候,pred肯定不会为null,因为前面的main线程已经设置了tail节点,此时的pred就是指的是main线程设置的tail节点,就会进入条件判断语句,走acquireQueued方法,将main线程设置的tail节点也就是当前节点的pred节点的waitStatus设置为-1,当前节点的waitStatus还是0,最终其实就会变成这样:

组成了一个队列,采用尾插法,那么问题又来了,这个队列在并发的情况下会越来越长,什么时候唤醒并出队呢,不然多耗内存啊?是不是,不急不急,我们来看看,如果这个时候刚好thread-1线程业务逻辑执行完了,要释放锁(还是两个线程执行的节点来分析,不加入第三个线程,原理是一样的),那么请看:

接着进入release方法:

tryRelease肯定返回的是true对不对,因为只有thread-1抢到了锁,所以他肯定能释放成功,那么就会走下面的判断,此时我们的head!=null,但是h.waitStatus为多少呢,还记不记得我们在main线程执行抢锁逻辑时执行了shouldParkAfterFailedAcquire这个方法,外层是一个死循环,在第二次循环时,将pred改为了-1啊,其实pred节点就是head节点,那么我们是不是就可以愉快的往下走了,我们接着往下看,进入unparkSuccessor方法java.util.concurrent.locks.AbstractQueuedSynchronizer.unparkSuccessor(Node):

我们来分析一下,ws肯定是为-1的,此时传入的node即为main线程设置的head节点,进入方法compareAndSetWaitStatus,将head节点的waitStatus设置成0,s即为第二个节点,如果s不为null,则唤醒这个节点的线程,为什么是第二个节点而不是head节点呢,因为我们在第二个线程进入时,才会生成head和tail节点,而我们的head节点里面是是没有线程信息的,只有tail节点里面才有,也就是这个node.next:

唤醒了tail节点,也就是main线程创建的Node节点,这个时候,main线程还是会接着执行如下方法:

因为是在这儿阻塞的,那么就返回true,然后接着执行:

设置interrupted=true,开始第三次循环,因为第一次将pred的waitStatus设置为了-1,第二次循环就阻塞住了,所以是第三次循环,此时p就是head节点,并且当前节点也就是tail节点肯定是能加锁成功的,并将state设置成1,并设置锁持有者线程为当前线程,接着:

设置当前节点为head节点,线程设置成null,pre节点也设置成null,原先的head节点next节点设置成null,p.next = null; // help GC,帮助GC回收原head节点,返回true,当此线程执行完业务逻辑之后,进入unlock逻辑,还是进入release方法:

当前节点就是head节点,所以h != null && h.waitStatus != 0一定是为true的,进入unparkSuccessor方法:

此时,将head节点的waitStatus改为1,node.next肯定是为null的,for循环中tail节点就是当前head节点,所以t!=node不成立,结束。。。。。。

6、lock.lock()线程重入代码(重入,非公平锁)


package com.zhangmen.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

	private static Lock lock = new ReentrantLock();

	public static void main(String[] args) {
		// // 多线程调用
		// new Thread(() -> {
		// doSomeThing();
		// }, "thread-1").start();
		// System.out.println("thread-1已经执行完毕,下面main线程开始执行......");
		// 主线程调用
		mainThreadTest();

	}

	private static void mainThreadTest() {
		doSomeThing();
	}

	private static void doSomeThing() {
		System.out.println("-------------------------------");
		lock.lock();
		lock.lock();
		try {
			System.out.println("做点什么......");
			Thread.sleep(100000000);
		} catch (Exception e) {
			// ignored
		} finally {
			lock.unlock();
			lock.unlock();
		}
	}
}

第二次lock的时候,进入acquire方法:

tryAcquire返回true,所以取反就是false,状态+1成功,lock成功

7、lock.lock()公平锁

其实就大差不差,一进来就会调用lock方法java.util.concurrent.locks.ReentrantLock.FairSync.lock():

有没有很面熟,然后接着调用acquire方法,套路都是差不多的:

然后接着调用tryAcquire方法,注意这个时候就会调用java.util.concurrent.locks.ReentrantLock.FairSync.tryAcquire(int)这个方法:

你看其实都一样的,只是多了hasQueuedPredecessors这个方法,这是方法的主要作用是什么呢,等我再去研究研究,拜拜了各位~~~~~

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值