关于JUC的一些概述(一)

目录

绪论

一.AQS(AbstractQueuedSynchronizer)

1.AQS原理

二.ReentrantLock

 1.ReentrantLock概述

2.ReentrantLock原理

3.条件变量实现原理 


绪论

        JUC是指Java.Util.concurrent包的简称,其下提供了很多支持高并发的方法接口。故在此做一章来总结复习一下学到的有关JUC的一些概念

一.AQS(AbstractQueuedSynchronizer)

        AQS是在juc包下的locks包下,是一个用来构建锁和同步器的框架,他是许多我们使用的锁的底层实现,比如ReentrantLock, Semaphore...其实个人感觉它更像synchronize锁底层的Monitor,只不过Monitor的底层是C++写的,而AQS是基于JAVA编写的类。(后面会再复习synchronize底层的原理)

1.AQS原理

        AQS的核心思想其实就和加锁的一个过程息息相关:

当请求共享资源处于空闲状态时,则将本次请求资源的线程设置为有效的工作线程(Owenr),并且该共享资源设置为加锁状态。

当请求共享资源处于加锁状态时,则将本次请求资源的线程阻塞,并且需要提供一套线程阻塞、等待以及被唤醒的机制。(这个机制是由维护一个阻塞队列(CLH)实现的)        

看一个AQS的原理图:

 CLH是一种虚拟的双向队列,其并不存在队列的实例,而是通过结点之间的连接构成了双向的队列。当线程阻塞时,等待线程会加入到CLH阻塞队列中等待(底层使用的park),而资源是否被占用是根据state的状态来决定。

依据上面所说的AQS的核心思想,我们可以得到一些需要解决的几个要点,并在这里给出AQS的解决方法。

  1. 如何决定资源是否空闲?
  2. 如何提供一套线程阻塞、等待以及唤醒的机制?

问题1:

        AQS中使用了一个int变量state来决定共享资源的状态。(前人尝试过long类型的state,发现性能并不是很好)state变量由volatile修饰保证了其可见性,并且在对state赋值时,AQS都是使用的CAS进行查询赋值,因此保证了其原子性。

        state状态的取值不同,其状态也不同,而不同的实现对state的状态值的定义也不同。如ReentrantReadWriteLock会将state分为高16位和低16位分别为读锁和写锁的状态。

问题2:

        AQS维护了一个FIFO的CLH,当状态处于阻塞时,会将阻塞的线程加入到队列中,并且用park将该线程进行阻塞,此时若工作线程释放了锁,则阻塞队列中的第一个线程(离head最近的线程,通常队列会有一个占位结点,因该为该节点的下一个结点)将会被unpark唤醒,然后加入对锁的竞争中(不一定竞争成功,若竞争失败则重新恢复到阻塞队列)

主要用到的AQS的并发工具类:

接下来一次介绍这些工具类:

二.ReentrantLock

 1.ReentrantLock概述

        ReentrantLock是一个可重入、可打断的,用来协调多线程对共享对象、变量的访问,保证了代码块内的可见性和原子性。还可以设置多个条件变量来进行await、signal的同步调用。其具体与synchronize的区别放在后面复习synchronize的时候再做总结。

2.ReentrantLock原理

 以非公平锁(默认构造非公平锁)为例:

(1)当没有竞争时,线程0成功请求共享资源,NonfairSync(继承自AQS)的state会被CAS修改成1.

(2)当第一个竞争出现,线程1执行了:

a.将尝试CAS修改state的状态(从0->1),但明显失败了

b.此时将会进入到tryAcquire逻辑,但是此时state还是1(线程0没有释放资源),因此还是会失败

c.接下来进入addWaiter逻辑,构造Node队列(CLH)。

        Node结点包含了多个信息:线程信息、当前结点的waitStatus状态信息。这个状态信息可以用来判断该结点是否能够唤醒其下一个结点,若为-1则需要唤醒,默认为0。

        Node结点的创建是懒惰的

d.然后会将node(当前结点,也即线程1所对应的Node)的前驱结点的waiteStatus设置为-1,代表线程1是需要被唤醒的

 

 e.设置完后,进入parkAndCheckInterrupt,将线程1park,灰色状态(中间其实还会重复去获取锁,以及设置前驱结点的状态为-1时,也会再次去获取,线程1其实总共尝试去获取锁3次,这里省去细节。具体可以看源码)

 若此时有多个线程经历上述过程竞争失败,会变成这样

此时若线程0释放锁(tryRelease),如果成功会将state=0,并且将exclusiveOwnerThread=null

 当队列不为null时,并且head的waitStatus = -1,会进入unpark阶段,此时会找到离head最近的那个结点,用unpark将其唤醒,并且将其前驱从队列中去除,而自己的这个结点的线程信息会变成null。

此时线程1会重新参与竞争(如果存在竞争)

如果竞争成功会将state重新cas成1,并将exclusiveOwnerThread设置为自己。

如果竞争失败,会恢复到阻塞队列中,重新进入park阻塞

当然,如果是公平锁,则线程4会加入到阻塞队列中,而线程1则会直接拿到锁,没有竞争。

以上只是展现了ReentrantLock的非公平锁的一个过程,而可重入的特性并没有展现,其实可重入就是在变成锁的持有者时exclusiveOwnerThread=now,如果此时再获得到锁,则会使state的值再往上加。而阻塞队列中的线程想要获得unpark,必须等到state==0后才可以有机会

3.条件变量实现原理 

每个条件变量其实就对应着一个等待队列,其实现类是ConditionObject

 假若当前线程0持有锁,调用await时,会创建一个新的Node,状态为-2,线程信息关联线程0,加入到等待队列的尾部,此时AQS会进行一个fullRelase,释放同步器上的锁。

 随后会unparkAQS阻塞队列中的下一个结点竞争锁,假设没有竞争,则线程1获得锁

 此时park阻塞住线程0,使其进入阻塞状态。此时需要由锁的持有者来进行signal唤醒

此时如果线程1调用了signal,则会将等待队列中的第一个Node取得,并将该Node加入到AQS阻塞队列的尾部,并将waitStatus设置为0(因为阻塞队列尾部的状态都是0,-1代表的是后面的结点需要被唤醒),线程3对应的Node的waitStatus设置为-1.

以上就是ReentrantLock的实现原理,其中可打断等原理就不做具体分析,看源码即可。

明天再总结关于读写锁、信号量Semaphore、计数器countdownLock相关概念 

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值