基本概念
线程实现
- 继承Thread
- 实现Runnable接口
(其他如FutureTask、lambda 表达式都是基于以上两种方式的包装或扩展)
线程基本方法
- sleep
- yield(让出一轮cpu时间片)
- join 等待当前线程完成
- interrupt——设置中止标志
- interrupted:查询中止状态并重置
- isInterrupted:查询中止状态
- 如果线程被join、wait、sleep阻塞,设置阻塞标志后将抛出异常,并重置标志
- 如果线程被阻塞在InterruptibleChannel上,将抛出异常
- 如果线程被阻塞在Selector上,线程将立即返回,类似调用Selector的wakeup方法
线程状态
线程同步
synchronized
用法
- synchronized方法
实例方法等同于synchronized(this)
静态方法等同于synchronized(T.class) - synchronized代码块
特性
- 原子性
- 可见性
- 可重入性
- 禁止指令重排序
- 异常的锁——程序中出现异常,锁会被释放
底层实现(锁升级)
- 锁定的是对象,而不是代码
- 对象头markword记录线程id(偏向锁)->自旋锁(cas)->重量级锁
jdk15 将禁用偏向锁,默认不打开
注意事项
- == 不能使用String和基础数据类型的装箱类型==
- 禁止修改锁的对象(修改对象引用)
public class D01 implements Runnable {
private Object lock=new Object();
private int count;
public static void main(String[] args) {
int size=20;
D01 d01 = new D01();
Thread[] t=new Thread[size];
for (int i = 0; i < size; i++) {
t[i]=new Thread(d01, "t"+i);
}
System.out.println("启动线程。。。。 " );
for (int i = 0; i <size ; i++) {
t[i].start();
}
for (int i = 0; i <50 ; i++) {
d01.modifyLock();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// d01.printCount();
}
public D01(){
this.count=0;
}
public void modifyLock(){
lock=new D01();
System.out.println("修改锁对象引用。。。。 " );
}
public void printCount(){
System.out.println("thread:"+ Thread.currentThread().getName()+ "count = " + count);
}
@Override
public void run() {
synchronized (lock){
for (int i = 0; i <100_0000 ; i++) {
count++;
}
printCount();
}
}
}
volatile
特性
- 线程之间可见性——缓存一致性协议
- 禁止指令重排序——内存屏障实现
- 不保证原子性
CAS
- compare and set/swap
- CPU指令支持
- ABA问题,加上版本号解决
线程1 x:a->b
线程2 x:b->a
线程1把变量值由A改为B,然后线程2又把变量值由B改成A
AQS(AbstractQueuedSynchronizer)
概况
- 为基于FIFO等待队列的同步器提供了基本的框架实现
- 适用于依赖单原子值(atomic int value)来表示状态(获得/释放锁)的同步器,子类负责维护state字段
- 支持互斥锁和共享锁
- 核心是变种的CLH自旋锁
- 使用varhandle更新对象字段值(jdk>=1.9)
CLH自旋锁
- 线程等待分为被动等待和主动等待(自旋),被动等待注册锁请求,锁释放后,注册的线程就会被唤醒;主动等待就是不停的的检测锁的状态,直到竞争到锁。直观感觉,自旋肯定比被动等待浪费性能,但是对于小任务,空转时间比较短,反而比被动等待的代价小,CLH锁就是一种自旋锁,它在本地变量上自旋,不断检查前驱状态
- CLH采用FIFO的单向链表结构,但是AQS使用的是CLH的一种变体,采用双向链表,pre链用于线程被取消;next链用于阻塞实现,当通知后续节点时,当后续节点为空时,通过原子更新tail指针来避免与新加入的的节点竞争
- 自旋后升级为LockSuport.park
wait/notify
- wait必须先拥有锁,然后释放锁,等待其他线程通过notify/notifyall 唤醒,重新获取锁,才能恢复执行,一般与synchronized一起使用
- notify唤醒通过wait被阻碍的线程,被唤醒的线程需要当前线程释放锁,然后重新获得锁才能继续执行,如果存在多个被阻塞的线程,线程的选择将依赖于系统的调度策略
public class WaitDemo {
private static Object lock = new Object();
public static void main(String[] args) {
Thread t1=new Thread(()->{
try {
synchronized (lock){
System.out.println("t1 run....." );
Thread.sleep(5000);
lock.notify();
System.out.println("t1 notify and sleep....." );
Thread.sleep(5000);
}
} catch (Exception e) {
e.printStackTrace();
}
});
Thread t2=new Thread(()->{
try {
synchronized (lock){
System.out.println("t2 lock wait....." );
lock.wait();
System.out.println("t2 pass wait and sleep....." );
Thread.sleep(5000);
}
System.out.println("t2 run after wait....." );
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t2.start();
t1.start();
}
}
AtomicXXX
- CAS
LongAdder
- 内部使用分段锁
LockSupport
- 创建其他同步器的基础类
- 主要方法位park/unpark
ReentrantLock
- AQS
- 具有公平锁和非公平锁,对于公平锁,如果存在阻塞队列,则加入阻塞队列,否则直接竞争
ReadWriteLock
- AQS
- 共享排他锁
Semaphore
- 限制访问资源的线程数量
- AQS
Exchange
- 适用于遗传算法和管道设计
- dual data structure
public class ExchangDemo {
public static void main(String[] args) {
Exchanger<ArrayList<Integer>> exchanger = new Exchanger<ArrayList<Integer>>();
Runnable writerTask= new Runnable() {
@Override
public void run() {
ArrayList<Integer> writerList = new ArrayList<>(10);
Random random = new Random();
while (true){
if(writerList.size()>=10){
try {
System.out.println("exchanger..... ");
writerList = exchanger.exchange(writerList);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
int i = random.nextInt();
System.out.println("add data " + i);
writerList.add(i);
}
}
}
};
Runnable readTask= new Runnable() {
@Override
public void run() {
ArrayList<Integer> readerList = new ArrayList<>(10);
while (true){
if(readerList.isEmpty()){
try {
readerList=exchanger.exchange(readerList);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
System.out.println("remove data " + readerList.remove(0));
}
}
}
};
new Thread(readTask).start();
new Thread(writerTask).start();
}
}
CountDownLatch
- await/coundDown
- AQS
- 参与数不可变,不可重用
CyclicBarrier
- reentranlock
- 参与数不可变,可重用
- 采用all-or-none模型,要么全部成功,要么全部失败,即一个线程在await之前中止、失败或超时,其他await的线程将中止
- barrier action由最后到达barrier point的线程执行
public class CBDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(10,()->{
System.out.println(" cyclicbarrier action run .... in "+ Thread.currentThread().getName());
});
int i1 = new Random().nextInt(10);
String threadName="thread"+i1;
System.out.println("execption threadName = " + threadName);
Thread[] threads = new Thread[10];
for (int i = 0; i <10 ; i++) {
threads[i]=new Thread(()->{
System.out.println("process thread task.... in "+Thread.currentThread().getName());
// if(threadName.equals(Thread.currentThread().getName())){
// int r= 1/0;
// }
try {
Thread.sleep(new Random().nextInt(500));
if(cyclicBarrier.await()==0){
System.out.println("exec barrier point task....");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},"thread"+i);
}
for (Thread thread : threads) {
thread.start();
}
}
}
Phaser
- ManagedBlocker
- 可重用,参与的parties 数可变
- 当存在线程中止时,不会导致其他wait的线程也被中止,可以执行响应的恢复任务
- phaser支持树形结构,通过分组减少竞争,提高吞吐量
public class PhaseDemo {
public static void main(String[] args) throws InterruptedException {
cleanHouse();
}
private static void cleanHouse(){
Phaser phaserMain = new Phaser(1);
Phaser phaserWindow = new Phaser(phaserMain);
Phaser phaserDesk = new Phaser(phaserMain);
Runnable taskClearDesk = new Runnable(){
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我擦完桌子...in " + Thread.currentThread().getName());
phaserDesk.arriveAndDeregister();
}
};
Runnable taskClearWindow = new Runnable(){
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我擦完窗户...in " + Thread.currentThread().getName());
phaserWindow.arriveAndDeregister();
}
};
Runnable taskClearFloor = new Runnable(){
@Override
public void run() {
//phaserMain.awaitAdvance(0);
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我扫完地...in " + Thread.currentThread().getName());
phaserMain.arriveAndDeregister();
}
};
Runnable taskClearRubbish = new Runnable(){
@Override
public void run() {
//phaserMain.awaitAdvance(1);
System.out.println("我扔完垃圾...in " + Thread.currentThread().getName());
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
phaserMain.arriveAndDeregister();
}
};
for (int i = 0; i <4 ; i++) {
phaserWindow.register();
new Thread(taskClearWindow).start();
}
for (int i = 0; i <6 ; i++) {
phaserDesk.register();
new Thread(taskClearDesk).start();
}
phaserMain.arriveAndAwaitAdvance();
for (int i = 0; i <8 ; i++) {
phaserMain.register();
new Thread(taskClearFloor).start();
}
phaserMain.arriveAndAwaitAdvance();
for (int i = 0; i <3 ; i++) {
phaserMain.register();
new Thread(taskClearRubbish).start();
}
phaserMain.arriveAndAwaitAdvance();
System.out.println("房屋打扫完成。。。。。");
}
}
锁的选择
- 执行时间比较短线程数少用自旋锁
- 执行时间比较长线程数多用系统锁
锁的优化
- 锁定细化——竞争激烈的情况下,尽量锁定关键的临界代码
- 锁的膨胀——频繁的获取锁,很消耗资源,可以把相应的代码合并
ThreadLocal
引用类型
- 强——没有引用就会被回收
- 弱——当WeakReference 引用的对象,没有其他强引用时,它不会阻止gc回收改对象
- 软——SoftReference 内存不够用时,被回收,主要用于缓存
- 虚引用——PhantomReference 管理堆外内存
关键点
- ThreadLocal基本上是个代理类,set/get都是操作的当前线程的ThreadLocalMap属性
- ThreadLocalMap 以ThreadLocal为key,同时ThreadLocalMap中的Entry扩展自WeakRefercence,所以当ThreadLocal实例无其他外部引用时,该key会被回收,对应的value 无法访问,从而会出现内存泄漏,所以set对象用完后,remove(是否value也可以设计成弱引用?)
- ThreadLocalMap中是以ThreadLocal的HashCode值取低位(容量长度-1位)来定位Hash桶的,而该hash值是魔数(0x61c88647)的n-1倍,为什么要使用0x61c88647?因为基于斐波那契哈希方法( Fibonacci Hashing)的散列值分布比较均匀
h ( x ) = ⌊ M W ( a x m o d W ) ⌋ M = 2 k W = 2 w a = ϕ − 1 W ϕ = 1 + 5 2 golden ratio(黄金比率) h(x)=\lfloor\frac M W(ax \mod W)\rfloor \\ M=2^k \\ W=2^w \\ a=\phi ^{-1}W \\ \phi=\frac {1+\sqrt5} 2 \tag*{golden ratio(黄金比率)} h(x)=⌊WM(axmodW)⌋M=2kW=2wa=ϕ−1Wϕ=21+5golden ratio(黄金比率)
- ThreadLocalMap内部使用数组来存储Hash桶,容量为2^n,当实际存储量达到容量的三分之二时,将进行扩容
- 使用开放定址法解决hash冲突
同步容器
容器概览
明细
List
- CopyOnWriteArrayList
- 线程安全的ArrayList变体
- 任何对数组的改变(添加、删除)都会生成新的数组
- 迭代器(iterator)只是数组的一个镜像,不会反应数组的实时变化,同时迭代器也不支持增删等改变数组的操作
- 适合于读多写少的应用场景
Queue
BlockingQueue
- 添加等待队列非空的take和等待队列有存储空间的put操作
- 不支持插入null元素
- 是否存在容量限制依据具体实现
- 排队和出队都是线程安全的,但是批量操作(addAll、containsAll、retainAll和removeAll)并不一定是线程安全的,依赖于具体的实现
- 主要用于生产者-消费者应用场景
- happen-before原则:一个线程的入队操作优先于另外一个线程的出队(获取元素或删除元素)操作
下表为当队列满或空时,相应的入队或出队操作的结果:
抛出异常 | 返回特别值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | add | offer | put | offer |
删除 | remove | poll | take | poll |
检索而不删除 | element | peek |
- ArrayBlockingQueue
1.1 有界队列
1.2 入队和出队使用一把锁 - ConcurrentLinkedQueue
2.1 使用无等待算法,内部使用CAS锁
2.2 由于是无界队列,所以不存在入队及出队阻塞问题(无put/take方法)
2.3 size属性可能不准确,因为需要遍历整个链表来获取size,而同时可能存在出入对操作
2.4 队列中的头和尾指针是延后更新的(入队和出队并不会同时更新head和tail指针)
2.5 采用单链表结构,头部存在一个哨兵节点(降低读写竞争) - DelayQueue
线程安全的PriorityQueue - LinkedBlockingDeque
4.1 双向链表,近似无界
4.2 采用单锁,可双向操作 - LinkedBlockingQueue
5.1 采用单链表结构,头部存在哨兵节点(降低读写竞争),近似无界
5.2 采用双锁算法,读写两把锁 - LinkedTransferQueue
6.1 采用双队列(dual queue),队列中包括请求节点和数据节点
6.2 适用于生产者需要确认消费者收到消息 - PriorityBlockingQueue
7.1 单锁
7.2 采用基于数组的堆排序算法 - SynchronousQueue
8.1 采用双队列
8.2 生产者与消费者同步的,生产者等待消费才能继续,消费者等待生产者才能继续
8.3 适合于管道等类似场景应用
8.4 采用自旋加park方式阻塞
Set
- ConcurrentSkipListSet
- 基于ConcurrentSkipListMap的Set
- CopyOnWriteArraySet
- 基于CopyOnWriteArrayList的Set
- 具有CopyOnWriteArrayList相似的特点
Map
ConcurrentMap
-
ConcurrentHashMap
1.1 内部采用数组实现,当出现Hash冲突时,相同Hash则以链表存储,数量超过8则转换为红黑树
1.2 数组的大小为2n,当空间使用超过75%,则按2n+1进行扩容
1.3 在更新时,如出现Hash冲突,会使用synchronized锁住该Hash值链上的第一个节点
1.4 主要应用于读多写少场景,保证并发读的同时尽量减小数据更新竞争影响
1.5 能提前预估数量容量,将会减少扩容的次数 -
ConcurrentSkipListMap
2.1 基于链表的跳表变体
2.2 25%的节点被索引,即增加层级的概率为0.25(随机数与0x80000001与等于0,排除负数(负数高位为1)和奇数)
2.3 查询和更新的时间复杂度均为logn
2.4 节点删除采用懒删除,先标记逻辑删除,最后物理删除
线程池
线程池作用
- 任务调度优化
- 异步任务跟踪
- 资源管理
线程相关接口
任务接口
- 无返回值——Runnable
- 有返回值——Callable
线程池接口
- 负责任务调度——Executor
- ExecutorService
是对Executor接口的扩展,提供了生命周期管理(shutdown)以及异步任务的跟踪
异步接口
-
异步任务的执行结果——Future
-
FutureTask
同时实现了Runnable和Future接口,一个可以被执行的Future -
CompletableFuture
一个可以显示设置任务的计算结果及状态的Future,同时支持当任务完成时,触发相应的操作
线程池分类
ThreadPoolExecutor
参数说明
核心线程数 | 最大线程数 | 线程创建工厂 | 线程空闲时间及单位 | 任务队列 | 任务拒绝策略 |
---|---|---|---|---|---|
corePoolSize | maximumPoolSize | threadFactory | keepAliveTime/unit | workQueue | handler |
线程池中一致保持的线程数量,当当前线程小于核心线程数,新到任务将创建新的线程处理 | 线程池最大的线程数量,当大于核心线程小于最大线程数时,如果队列未满,新到任务排队,如果队列已满,创建新的线程处理 | 当所有线程都忙并且任务队列已满,新到任务的处理方式,主要抛异常、调用者运行、丢弃、丢弃最老的、自定义 |
特点
单队列
ForkJoinPool
- 多队列
- 任务分解(fork)与汇总(join)
- 用很小的线程可以执行很多任务
- 计算密集型
工厂类Executors
ThreadPoolExecutor 类
- newSingleThreadExecutor
- newFixedThreadPool
- newCachedThreadPool
- newScheduledThreadPool
ForkJoinPool类
- newWorkStealingPool