Java 高并发核心编程(尼恩编著) 阅读提纲
-
进程与线程
- 进程: 系统并发调度的最小单位
- 线程: CPU调度的最小单位、相当于进程代码段的一次顺序执行、调度效率高于进程调度
- 进程与线程的区别
- 线程是进程代码段的一次顺序执行、一个进程最少拥有一个线程
- 进程是 系统调度的最小单位、线程是CPU调度的最小单位
- 线程 是 基于高并发衍生出来的、充分发挥CPU性能、弥补进程却换过于笨重的问题
- 切换速度不同、线程切换速度高于进程
- 进程之间是相互独立的、线程之间不相互独立、方法去、堆内存、系统资源是共享的
-
线程
-
创建方式
- Thread (继承)
- Runable (实现)
- Callable (实现、具有返回值)
- FutureTask (同步阻塞获取返回值)
- ThreadPool
- Executes
- 创建线程的方法
- 单线程池 newSingleThreadPool
- 特点: 只有一个线程工作、保证任务顺序执行
- 缺点: 阻塞队列 为 LinkedBlockingQueue 无界阻塞队列、处理速度小于 任务新增的速度、会导致队列过大、容易造成 OOM 异常
- 固定线程池 newFixedThreadPool
- 特点: 新线程没有达到固定线程、新任务进来就会创建线程去执行、否则加入阻塞队列、任务结束回收线程
- 缺陷: 阻塞队列 为 LinkedBlockingQueue 无界阻塞队列、处理速度小于 任务新增的速度、会导致队列过大、容易造成 OOM 异常
- 调度线程池 newScheduleThreadPool
- 特点: 延时、周期性的调度线程
- 缺陷: 阻塞队列为delayedWorkQueue 、线程不设上限、如果大量任务到期、会导致大量线程被创建、CPU 线程资源耗尽
- 可缓存线程池 newCacheThreadPool
- 特点: 新任务进来会一直创建线程、直到达到JVM 或者系统最大线程、任务长时间不执行就会被回收
- 缺陷: 一致创建线程、会导致大量线程被创建、容易导致OOM、严重会导致 CPU线程资源被耗尽、阻塞队列为 synchronousQueue 同步队列
- 单线程池 newSingleThreadPool
- 创建线程的方法
- ThreadPool
- 创建方式 newThreadPoolExecutor(coreSize,maxSize,keepAliveTime,timeUtil,blockQueue,threadFactory,rejectHandler)
- 参数详解
- coreSize 核心线程数大小(新任务进来、已创建线程没超过核心最大线程数、会优先创建线程、不论已有线程是否有正在执行的任务)
- maxSize 最大线程数 (核心线程数已满且不处于空闲状态、阻塞队列已满、就会创建线程、工作线程不能大于最大线程)
- keepAlive 线程存活时间、非核心线程、空闲时间超过最大存活时间就会被回收
- timeUtil 存活单位
- blockQueue 阻塞队列 存放异步线程、核心线程已满、无空闲线程、新任务进来就会被存入阻塞队列
- ArrayBlockQueue 有界队列 FIFO
- LinkedBlockQueue 不设置大小为无界队列、FIFO
- PriorityBlockQueue 优先级队列
- DelayQueue 延时队列、超过期限出列
- SynchronousQueue 同步队列、不存储元素、插入操作 必须等待另一个元素的移除操作
- threadFactory 线程工厂、维护线程的信息
- rejectHandler 拒绝策略
- abrot 线程池已满就抛出异常
- discard 抛弃策略: 不会抛出异常
- discardOld 抛弃最老任务策略: 队列已满就会抛弃最老任务、一般是队头
- CallerRuns 调用者执行策略 : 队列已满、用调用者线程执行本次任务
- 自定义策略
- 调用流程
- 新任务进来如果工作线程小于核心线程、创建新线程执行任务、不论核心线程(工作线程)是否有空闲、核心线程已满且无空闲工作线程、加入阻塞队列、如果核心线程已满、阻塞线程已满、再次创建线程执行、线程数不能大于最大线程数、超过最大线程数、走拒绝策略
- 钩子方法
- beforeExe 异步任务执行前执行
- afterExe 异步任务执行后执行
- terminated 线程中断执行
- Executes
-
生命周期
- new 新建
- runabel 就绪
- running 运行
- block 阻塞
- waiting 限时等待
- destory 销毁
-
threadLocal
-
定义: 共享变量都有一个线程自己独有的本地值
-
使用场景:
- 线程隔离
- 跨函数参数传递
-
结构 Map
- key : threadLocal
- value : 本地私有值
-
-
线程之间通信
-
wait
- JVM 将 线程加入 锁对象监视器的 等待集 (waitSet)、等待其他线程唤起
- 放弃锁对象的 owner 权限、其他线程可以抢夺监视器 权限
- 线程状态更新为 waiting
-
notify
- JVM 将 waitSet中第一个对象(notifyAll 是所有对象)移到 entryList中
- 将线程状态由 waiting 改为 blocking,具备监视器的抢夺权限
- 抢夺成功后、线程状态由 blocking 改为 runnable 具备重新执行的资格
-
-
内置锁
-
i++ 线程安全问题
- 步骤: 内存取值、寄存器元素、内存赋值
- 多个操作、可能存在先读后写的操作、取到的值无法保证是内存的值、导致值不一致
-
synchronized
- 锁类型
- 实例对象锁
- 类锁(static)
-
锁状态
-
无锁
- 对象头标记: 偏向锁 0,锁状态 01
-
偏向锁
- 对象头标记: 偏向锁 1, 锁状态 01
- 解决问题: 无竞争状态下锁竞争问题(没有其他线程抢锁)
- 原理: 不存在锁竞争的一个线程获取到了锁、锁就进入了偏向锁状态、记录线程ID、对象锁改为01,偏向改为1
-
偏向锁撤销
- 在一个安全点停止线程
- 修复锁记录指向 mark word 、 清除线程id
- 将当前锁升级为轻量级锁
- 唤醒当前线程
-
偏向锁升级(膨胀)
- 一旦有第二线程去抢占这个锁、看到锁状态为偏向锁状态、JVM检查原来占有线程是否存活、挂了就将状态改为无锁状态、然后进行重新偏向、偏向为抢锁线程
- 存活、进一步检查占有线程的栈信息是否通过锁记录持有偏向锁、存在记录、就表明 线程还在使用偏向锁、发生锁竞争、撤销偏向锁、将偏向锁升级为轻量级锁
-
轻量级锁
-
对象头标记: 锁状态 00
-
抢锁行为: 当锁为偏向锁、另一个线程试图抢占、锁升级为轻量级锁、抢锁线程进行自旋
-
自旋优化: 适应性自旋
-
-
重量级锁
-
对象头标记: 10
-
锁升级条件:
- 超过最大等待时间还没有获取到锁、线程进入等待、该锁膨胀为重量级锁、指向监视器对象、登记 和 管理排队线程
-
监视器
- 属性
- Cxq
- 所有请求锁线程先进入这个队列
- 原理: 抢锁线程通过 CAS 自旋获取不到锁就会进入 cxq
- entryList
- cxq中 拥有 候选资源的线程会被加入 entryList
- waitSet
- 阻塞wait 方法 加入 waitSet,等待notify 唤醒、加入 entryList
- owner : 指向获取锁线程
- Cxq
- 属性
-
-
- 锁类型