7.1 AbstractQueuedSynchronizer -AQS
底层实现了双向链表,是队列的一种实现方式
对象创建以后其状态就不能修改
底层是双向链表,队列的一种实现
sync queue:同步队列,head节点主要负责后面的调度
Condition queue:单向链表,不是必须的的,也可以有多个
设计原理
使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架
利用了一个int类型标示状态,有一个state的成员变量,表示获取锁的线程数(0没有线程获取锁,1有线程获取锁,大于1表示重入锁的数量),和一个同步组件ReentrantLock,
使用方法是继承,基于模板方法
子类通过继承并通过实现它的方法管理其状态{acquire和release}的方法操作状态
可以实现排它锁和共享锁的模式(独占、共享,子类只能实现其中一个)
具体实现的思路
1.首先 AQS内部维护了一个CLH队列,来管理锁
线程尝试获取锁,如果获取失败,则将等待信息等包装成一个Node结点,加入到同步队列Sync queue里
3.不断重新尝试获取锁(当前结点为head的直接后继才会 尝试),如果获取失败,则会阻塞自己,直到被唤醒
4.当持有锁的线程释放锁的时候,会唤醒队列中的后继线程
AQS同步组件
CountDownLatch(闭锁,通过一个计数保证线程是否需要一直阻塞 )
Semaphore(控制同一时间并发线程的数目)
CyclicBarrier(与CountDownLatch 相识 阻塞线程,可以重置计数器)
ReentrantLock
Condition
FutureTask
CountDownLatch
同步阻塞类,可以完成阻塞线程的功能
*&&&& CountDownLatch :闭锁,通过一个计数,判断线程是否阻塞
&&&& Semaphore:控制并发线程的数目
7.2 CountDownLatch
同步辅组类,完成阻塞当前线程的功能,给定了一个计数器,原子操作,计数器不能重置。
调用await()方法会使线程处于阻塞状态,直到其他线程调用CountDown()方法时,才继续执行
当计数器变为0的时候,所有等待的线程才会继续执行
使用场景:查询需要等待某个条件完成后才能继续执行后续操作(Ex:并行计算)拆分任务
7.3 Semaphore
并发访问控制线程个数(同步机制), 提供了两个方法,实现有限大小的链表大小
semaphore.acquire(); // 获取一个许可
semaphore.release(); // 释放一个许可
semaphore.acquire(n);//获取多个许可
semaphore.release(n); // 释放n个许可
使用场景:仅能提供有限访问的资源,比如数据库连接数
tryAcquire())//尝试获取一个许可
tryAcquire 四个带参方法
1 tryAcquire(long timeout, TimeUnit unit)
2 tryAcquire()
3 tryAcquire(int permits)
4 boolean tryAcquire(int permits, long timeout, TimeUnit unit)
7.4 CyclicBarrier
运行一组线程等待到一个公共的屏障点,实现多个线程相互等待,当每一个线程都就绪后,才执行下去,通过计数器实现的
多线程计算数据,最后合并的场景
CyclicBarrier 与CountDownLatch 的区别
1 CountDownLatch 的计数器只能使用一次,CyclicBarrier 可以使用reset()方法重置
2 CountDownLatch 实现一个或者n个线程需要等待其他线程执行某项操作后才能继续执行
CyclicBarrier 实现多个线程了多个线程相互等待,知道多个线程都满足了某个条件以后才继续执行
描述的多个线程内部的关系,多个线程都调用await()方法后才继续向下执行
提供方法获取阻塞线程的个数,知道阻塞的线程是否中断
CyclicBarrier 对象调用await() 等待多个线程都满足条件后,在往下面执行
//定义有5个线程同步等待
1) private static CyclicBarrierbarrier =new CyclicBarrier(5);
2 )
在5个线程都满足条件后,先执行 log.info("callback is running"); 在执行以后的代码
private static CyclicBarrierbarrier =new CyclicBarrier(5, () -> {
log.info("callback is running");
});
7.5 ReentrantLock 与锁
java 两类锁: 1 synchronized
2 JUC的 ReentrantLock
ReentrantLock 与synchronized 的区别
1 &&& 可重入性 两者都是可重入锁 ,同一线程进入一次 锁的计数器就自增1 ,锁的计数器下降为0 时才释放锁
2 &&& synchronized 是依赖jvm实现的(操作系统实现,难查源码),ReentrantLock 是依赖jdk实现的(用户实现)
3 &&& 两者性能差不多 ,推荐使用synchronized ,synchronized 优化借鉴了CAS技术,用户态解决加锁问题避免进入内核态 使线程阻塞
4 &&& synchronized 更方便它是编译器保证锁的加锁和释放的,ReentrantLock 手工释放和加锁,在finally释放锁
锁的细腻度和灵活度 ReentrantLock 更好
ReentrantLock 的独有的功能
1 ReentrantLock 可指定是公平锁和非公平锁 synchronized 只能是非公平锁
公平锁(先等待的线程先获得锁)
2 提供了一个Condition类,可以分组唤醒需要唤醒的线程
synchronized 唤醒一个要不全部唤醒
3 提供能够中断等待锁的线程的机制,lock.lockInterruptibly()
ReentrantLock 实现是一种自旋锁,通过循环调用cas操作自加操作,避免了线程进入内核态发生阻塞
synchronized 不会忘记释放锁
ReentrantLock 函数方法
tryLock():仅在调用时锁定未被另外一个线程保持的情况下获取锁定
tryLock(long timeout, TimeUnit unit) 如果锁定在给定的时间内没有被另一个线程保持,且当前线程没有被中断,则获取这个锁定
lockInterruptibly() 当前线程如果没有中断就获取锁定,如果已经中断就抛出异常
isLocked() 查询当前此锁定是否由任意线程保持,
ReentrantReadWriteLock
在没有任何 读写锁(ReadWrite)*的情况下才能取得写锁(Write)
StampedLock
版本和模式两个部分组成
控制锁的三种方式:
1 写
2 读
3 乐观读
锁获取的方法是一个数字,用锁的状态控制相关锁的状态的访问
数字0 表示没有写锁
读锁分为 悲观锁 和乐观锁
对吞吐量有巨大的改进,特别是读线程多的场景中下
StampedLock 对于加锁容易误用其他的方法
Condition
package com.mmall.concurrency.example.lock;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class LockExample6 {
public static void main(String[] args) {
ReentrantLock reentrantLock =new ReentrantLock();
Condition condition = reentrantLock.newCondition();
new Thread(() -> {
try {
reentrantLock.lock(); // 线程加入到AQS的等待队列中
log.info("wait signal"); // 1
condition.await(); //调用await()方法后 线程从AQS对列中溢出(锁的释放),进入到condition的等待队列中
}catch (InterruptedException e) {
e.printStackTrace();
}
log.info("get signal"); // 4
reentrantLock.unlock();
}).start();
new Thread(() -> {
reentrantLock.lock();
log.info("get lock"); // 2
try {
Thread.sleep(3000);
}catch (InterruptedException e) {
e.printStackTrace();
}
condition.signalAll();
log.info("send signal ~ "); // 3
reentrantLock.unlock();
}).start();
}
}
result:
- wait signal
- get lock
- send signal ~
- get signal
总结
1 只有少量竞争者的时候,synchronized是比较好的选择
2 竞争者不少,线程的数量可以预估的,ReentrantLock 是一个比较好的锁实现