概念
即分工,同步和互斥(线程安全)。同步和互斥的区别在同步描述等待而互斥描述竞态条件,互斥和同步一般同时实现。
线程安全的源头在于可见性,原子性以及有序性,互斥是解决线程安全的核心方案,实现互斥的核心技术是锁如lock和syn锁,当然也有一些无锁方案如threadLocal非共享变量,copyOnWrite以及CAS原子类
进程
多线程的线程处于并发还是并行取决于CPU的调度
线程
线程可以通过Runnable/thread对象以及FutureTask对象创建
Runnable是任务的抽象thread是线程的抽象,组合优于继承,Runnable对象将任务脱离了thread对象的继承体系,方便与线程池结合
FutureTask对象实现了Runnable接口以及future接口用于获取任务返回结果,构造入参是callable。所以FutureTask是一个任务类,需要thread线程承载任务运行
栈上下文切换时保留的状态包括局部变量表以及返回地址等信息
线程的run方法不能直接调用需要通过start方法调用,因为start方法会开启新的线程
线程中断通过interrupt方法实现,该方法会将运行中线程的中断状态置为true,将等待中的线程抛出中断异常。但是park方法进入的等待状态不会抛异常而是设置中断状态为true
线程有新建,运行,死亡,阻塞以及等待5个状态。
操作系统层面的就绪态,运行态以及BIO时的阻塞态在java层面都是运行态。
线程竞争锁失败则会进入阻塞状态。调用wait,join以及sleep方法则会进入等待状态
垃圾回收线程就是守护线程,守护线程会等待所有用户线程执行结束后才结束
共享模型-管程
syn锁分为对象锁和类锁
如果局部变量的作用域没有脱离当前方法,则该变量时线程安全的
对象头分为markword表示运行时数据以及klassword指向class对象两部分。
markword有普通状态,偏向锁状态,轻量锁以及重量锁状态。轻量锁以及重量锁状态时分别保存栈帧中锁记录地址以及monitor地址
monitor的原理包括owner线程,阻塞等锁队列以及等待队列
syn锁优化包括偏向锁只有一个线程加锁,轻量级锁各线程没有竞争以及自旋。此外还有如逃逸分析后的锁消除以及锁粗化。锁膨胀即加锁从偏向锁到重量锁的过程
wait方法会释放锁,而sleep不会释放锁,所以sleep结束后不会阻塞。应用场景为阻塞队列的实现以及保护性暂停的实现即将结果从一个线程传递到另一个线程
park类似于wait,不过unpark可以唤醒指定线程并且park中断时不会有中断异常。应用场景为顺序控制
死锁的四个必要条件为互斥,不可剥夺,请求保持以及循环等待,一般通过控制加锁顺序破坏循环等待条件。最优实现是在类中定义多把锁保证不同功能的线程安全
syn锁和lock锁的区别在于lock锁使用AQS实现而lock使用管程实现,lock锁支持公平锁以及设置超时时间等功能而syn锁支持对象锁和类锁,性能层面syn锁有锁膨胀优化,语法层面syn锁不需要手动解锁而lock锁需要在finally块中解锁
AQS包括state锁占用状态,owner锁线程,CLH阻塞等锁队列以及由condition对象提供的等待队列
共享模型-内存
源自类以及锁可以保证原子性,但是volatile不能
volatile通过对变量进行主存读写保证可见性
volatile通过读写的内存屏障实现禁止指令重排序以实现有序性
volatile的应用场景为犹豫模式以及单例模式,犹豫模式表示如果其他线程已经做了某件事,那么当前线程可以立即返回,volatile在单例模式中保证了创建对象的并发有序性
共享模型-无锁
CAS存在的ABA问题通过版本机制解决
CAS配合volatile以及while循环可以实现CAS锁
原子类包括原子整数,原子引用,原子数组,字段原子更新器以及原子累加器。
原子累加器通过cell数组以及base基础值实现值的并发累加。每个cell对象分配一个线程,并且cell对象还通过添加padding的方式解决缓存行的伪共享问题
在java中CAS通过unsafe类实现
共享模型-不可变类
不可变类通过无状态类以及保护性拷贝实现。
保护性拷贝占用内存,通过享元模式解决,Integer包装类的就使用享元模式。享元模式的关键在于状态数组的更新。该数组是一个原子数组
共享变量-工具
线程池相比普通线程提供了延时定时的功能
线程池的状态和线程数保存在内部的一个ctl变量中,shutdown状态会处理队列中剩余任务,stop状态不会执行队列剩余任务
线程池核心线程数设置为预期并发数乘以单任务的平均耗时
非核心线程执行完任务后会从任务队列中取任务执行
非核心线程在无任务执行后经过配置的keepAliveTime之后才结束
阻塞策略优选主线程直接执行,如果任务阻塞主线程太久,则应该将任务持久化到数据库
线程池包括threadPoolExecutor,ScheduledThreadPool以及ForkJoinPool。此外还有动态线程池以及优先级任务线程池的实现
FixedThreadPool以及singleThreadPool的核心线程数等于最大线程并且任务队列无限大,适合任务量已知的耗时任务
cachedThreadPool的核心线程数为0,任务队列长度为1以及最大线程数无限大,适合任务数密集但是执行时间较短的任务
ScheduledThreadPool实现了线程池的延迟以及调度功能而ForkJoinPool分治线程池将递归运算通过线程池实现
动态线程池只要包括实时参数调整以及内置监控告警两部分。实时参数调整通过监听参数变化并调整参数值,内置监控告警则通过spring的actuator模块以及prometheus实现。开源实现有DynamicTP
优先级任务线程池则通过传入优先级队列作为任务队列实现
线程池的执行流程为先加入任务队列,而tomcat的线程池为先执行非核心线程
线程池体现了享元模式,不同类型任务应该使用单独的线程池
线程池中线程异常时,对于submit任务则线程会复用并把异常封装进返回值,对于execute任务则会创建新线程
AQS的CLH队列的每一个节点都有对应的waitStatus状态,0表示该节点为最后一个节点,-1表示该节点有义务唤醒后继节点
AQS的state属性标识资源的状态,使用volatile修饰,有独占和共享两种模式,独占如ReentrantLock,共享锁包括countDownLatch和信号量
condition条件对象实现了wait和notify的功能,包含一个类似CLH的队列,节点的waitStatus状态为-2表示节点处于等待池中
lock锁的底层是AQS的acquire和release方法
ReentrantLock可以实现为公平,可重入以及不可中断锁。不可中断锁利用了park方法不会被interrupt方法中断的特性
读写锁包括ReentrantReadWriteLock以及StampedLock。StampedLock需要配合戳使用,除了读锁以及写锁外,StampedLock还有乐观读锁
信号量的CLH节点为共享模式,在前一个节点被唤醒时会被同时唤醒
countDownLatch以及cyclicBarries都用于线程间同步,但是cyclicBarries可以用重用而countDownLatch不可以
线程安全的集合类包括历史集合hashTable,collections装饰的线程安全集合以及JUC下的安全集合。JUC下的集合包括Blocking,copyOnWrite以及concurrent三类。
concurrent类如concurrentHashMap的迭代器都是安全失败,即使用copyOnWrite机制遍历修改前的数据,而hashMap的迭代器是快速失败的
concurrentHashMap在1.7中锁住了segment结构,size计算时先不加锁计算两次如果两次不相等则锁住所有segment重新结算
而1.8中锁住了链表的头节点,扩容阈值为0.75,迁移完成的链表头节点标识为ForwardingNode,如果扩容时put并且put的链表正在迁移则会阻塞,put的链表迁移完成则put线程会帮忙迁移其他链表.size的增加和计算遵循原子累加器的原理
阻塞队列的底层利用了锁的wait和notify方法,linkedBlockingQueue使用了2把锁用于put和get
copyOnWriteArrayList每次写数据时都会加锁拷贝数组
异步编程
completableFuture用于异步任务编排,底层使用ForkJoin线程池
非共享模型
访问threadLocal对象的线程都会有该对象的独立拷贝,通过getMap方法可以访问对应线程的threadLocalMap,该map的key是threadLocal对象的弱引用,value是threadLocal对象在该线程的副本值
threadLocalMap使用开放寻址法而非拉链法解决哈希冲突