首先来简单介绍一下JUC并发包下的各种同步锁
如何使用具体的可以参考这篇博文:Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
-
countDownLatch:给countDownLatch指定一个计数器,通过await来拴住线程,然后通过countdown进行--操作,直到为0时,继续执行await下面的方法
-
CyclicBarrier:像一个车,坐满了发车,指定一个参数,通过await来拴住线程,当线程全部到位了,才能同时接下去一起执行自己的操作
-
Semaphore:指定一个参数,进行限流,最多允许这个数的线程同时运行,还可以穿入boolean类型参数指定是否为公平锁
-
- 使用acquire方法获取锁执行权,获取到了参数就-1;
- 使用RELEASE释放资源,参数+1
- 高速收费站:8个车道,两个收费站,最多只允许两个车道的车同时进收费站
-
Phase:按阶段执行,比如结婚现场,第一阶段:全部人到齐了才能入席,第二阶段等全部人入席了才能开始吃饭,第三阶段:全部人吃完饭了才能离开。第四阶段:只让新郎新娘去洞房,其他人注销离场。第五阶段:这时候只剩新郎新娘两个线程了
-
readwriteLock:如果都是读,那么加共享锁(读锁),大家可以同时查看,效率高不阻塞,如果是写加排他锁(写锁),需要排队,会阻塞
-
exchange:两线程互相交换
-
locksupport:调用park拴住线程,其他地方调用unpark的时候,自己才打开门拴
-
这些锁都是基于AQS实现
AQS
(AbstractQueuedSynchronizer)抽象队列同步器,是一个用来构建锁和同步器的框架,它底层用了 CAS 技术来保证操作的原子性,使用volatile保证线程直接的可见性。它能够成为实现大部分同步需求的基础,也是 J.U.C 并发包同步的核心基础组件。
AQS核心
- AQS 队列在内部维护了一个 FIFO(先进先出) 的双向链表,在双向链表中,每个节点都有两个指针,分别指向直接前驱节点和直接后继节点。使用双向链表的优点之一,就是从任意一个节点开始都很容易访问它的前驱节点和后继节点。
- 还有两个核心变量,node和state
node
- node是一个内部类,竞争锁失败的线程会被封装成一个个node加入到队列当中
state变量
- state变量:通过这个state变量来判断线程是否拥有执行权,具体的用法看子类,比如像countdownLatch中state初始值就是给它设置的那个参数
AQS框架总览
- Lock Support:用来处理线程的等待和唤醒
通过reentrantlock的调用关系,来了解AQS
JUC包下的锁都是基于AQS实现的,比如reentrantlock,它内部维护了一个内部类Sync,这个类继承自AQS(AbstractQueuedSynchronizer
调用关系图
tryAcquire方法
-
如果我们创建对象时使用的是非公平锁,它就会创建sync的子类nonfairSync对象。当我们调用lock方法的时候,它实际调用的是nonfairSync的lock方法,它首先会用CAS尝试去获得锁(
如果是公平锁,直接调用AQS的acquire方法,而不是暴力去抢锁),没有抢到,就会调用,AQS的acquire方法,然后呢再回调用tryacquire方法尝试去获得锁,非公平锁重写了tryacquire,然后去调用它自己的nonfairTryAcquire方法再次直接CAS去获取锁(如果是公平锁,会先去判断AQS队列中是否人在排队,没有的话才会去获取锁,有的话自己也加入队列中排队),还是失败,就会把自己封装成node节点加入到队列当中,等待下次去竞争。 -
- 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。
- 公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
addwaiter方法
-
- 在 AQS 中,队列中的每个 Node 其实就是一个线程封装,当线程在tryAcquire()竞争锁失败之后,会调用acquire方法,将当前线程包装成Node 并加入到 AQS 队列中;获取锁的线程释放锁之后,会从队列中唤醒一个阻塞的 Node (也就是线程)(封装节点方式 核心!!!:插入节点,用的是 CAS 自旋插入,这是因为在 AQS 中会存在多个线程同时竞争资源的情况,进而一定会出现多个线程同时插入节点的操作,这里使用 CAS 自旋插入是为了保证操作的线程安全性,只用CAS锁尾巴那个节点,而不是整个队列,保证了效率)
-
-
- JDK1.9addwaiter方法中设置前置节点时,使用了varHandler
-
-
-
-
- varHandler作用:可以对普通属性做原子操作
- 比反射快,直接操作二进制码
-
-
acquireQueued入队方法
入队方式:
- 获取前驱节点
- 如果前一个节点是头节点,那么快轮到自己了,那么尝试去争抢锁
- 抢到了,说明前一个节点已经把锁释放了,那么就将自己设置为头节点
- 如果不是头节点,那么就等着前置节点叫醒你
- AQS 提供了两种锁,分别是独占锁和共享锁,独占锁指的是操作被认作一种独占操作,比如 ReentrantLock,它实现了独占锁的方法,而共享锁则指的是一个非独占操作,比如一些同步工具 CountDownLatch 和 Semaphore 等同步工具
-
- 独占锁的原理是如果有线程获取到锁,那么其它线程只能是获取锁失败,然后进入等待队列中等待被唤醒。
-
- AQS可以说是一个给予实现同步锁、同步器的一个框架,很多实现类都在它的的基础上构建的
- 在AQS中实现了对等待队列的默认实现