一:线程基础
线程与进程:进程是用来获取资源的单位,线程是用来调配资源的单位,一个进程可能有多个线程运行
多线程是为了提高程序运行效率.
线程生命周期
- NEW - 初始状态,一个新创建的线程,还没开始执行。
- RUNNABLE - 可执行的状态,要么是在执行,要么是一切就绪等待执行,例如等待分配CPU时间。
- WAITING - 等待状态,等待其他的线程去执行特定的动作,没有时间限制。
- TIMED_WAITING - 限时等待状态,等待其他的线程去执行特定的动作,这个是在一个指定的时间范围内。
- BLOCKED - 阻塞状态,等待锁,以便进入同步块儿。
- TERMINATED - 终止状态,线程执行结束。
线程问题:
线程安全问题
线程安全问题指的是,内存问题, 线程存在公共区域的数据被其他线程修改,导致数据存在安全问题.
解决措施:
- 加锁—保证同时只有一个人访问公共数据 —同步阻塞
- cas 操作,,但会有aba问题,数据被修改不知道—非同步阻塞
- 无同步方案,将线程限制在个线程范围内,这样无需同步也能保证数据不被征用.—ThreadLocal
使用多线程可能会有以下问题:
内存泄漏 上下文切换 死锁
上下文切换
一般程序设定的线程数量多于cpu核心数量,cpu采取的是分片方式执行线程,线程间切换就是上下文切换
死锁
多个线程抢占资源: 当前线程拥有其他线程需要的资源,当前线程等待其他线程已拥有的资 源,都不放弃⾃⼰拥有的资源。
解决方式
- 固定加锁的顺序,⽐如我们可以使⽤Hash值的⼤⼩来确定加锁的先后
- 尽可能缩减加锁的范围,等到操作共享变量的时候才加锁。
- 使⽤可释放的定时锁(⼀段时间申请不到锁的权限了,直接释放掉
开发中线程安全思考
- 能不能保证操作的原⼦性,考虑atomic包下的类够不够我们使⽤。
- 能不能保证操作的可⻅性,考虑volatile关键字够不够我们使⽤
- 如果涉及到对线程的控制(⽐如⼀次能使⽤多少个线程,当前线程触发的条件是否依赖其他线程的结果), 考虑CountDownLatch/Semaphore等等。
- 如果是集合,考虑java.util.concurrent包下的集合类。
如果synchronized⽆法满⾜,考虑lock包下的类
<br /> <br /> <br /> <br />
synchronized 关键字和 volatile 关键字的区别 synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!
volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定比 synchronized 关键字 要好。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码 块。
volatile 关键字能保证数据的可⻅性,但不能保证数据的原子性。 synchronized 关键字两 者都能保证。
volatile 关键字主要用于解决变量在多个线程之间的可⻅性,而 synchronized 关键字解决 的是多个线程之间访问资源的同步性。
常见实现多线程的方式
区别: Thrad 类不适合资源的共享, Runnable接口适合资源共享
- 继承Thrad 类
- 实现Runnable接口
- 有返回值的线程操作,实现Callable 接口,搭配线程池使用.返回Future对象
二:线程池
JVM在HotSpot的线程模型下,Java线程会⼀对⼀映射为内核线程 ,线程的创建与销毁会浪费大量时间,线程池为提高线程的复用性,以及固定线程的数量
ThreadPoolExecutor 相比 Executors 优点,更能了解线程池的运行规则,避免资源损耗
参数名 | 参数内容 |
---|---|
corePoolSize | 核心线程数量 |
maximumPoolSize | 最高线程数量 |
keepAliveTime | 当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最长时间 |
unit | keepAliveTime参数的时间单位 |
workQueue | 用于在执行任务之前保存任务的队列。此队列将仅保存由execute方法提交的Runnable任务。 |
threadFactory | 执行器创建新线程时使用的工厂 |
handler | 由于达到线程边界和队列容量而阻塞执行时使用的处理程序 |
三: ThreadLocal 线程共享变量
数据结构
- 什么是ThreadLocal:它提供了线程的局部变量,每个线程都可以通过set/get来对这个局部变量进⾏操作, 不会和其他线程的局部变量进⾏冲突,实现了线程的数据隔离。
- ThreadLocal实际⽤处(举例):Spring事务,ThreadLocal⾥存储Map,Key是DataSource,Value是 Connection
- ThreadLocal设计:Thread有ThreadLocalMap引⽤,ThreadLocal作为ThreadLocalMap的Key,set和get 进去的Value则是ThreadLocalMap的value
- ThreadLocal内存泄露:ThreadLocal被回收&&线程被复⽤&&线程复⽤后不再调⽤ThreadLocal的 set/get/remove⽅法 才可能发⽣内存泄露(条件还是相对苛刻)
- ThreadLocal最佳实践:⽤完就要remove掉
四: CompletableFuture 异步编排
4.1:创建异步对象
CompletableFuture 提供了四个静态方法来创建一个异步操作
1、runXxx 都是没有返回结果的,supplyXxxx都是可以获取返回结果的
2、可以传入自定义的线程池,否则就是用默认的线程池
3、根据方法的返回类型来判断是否该方法是否有返回类型
4.2计算完成时回调方法
whenComplete 可以处理正常和异常的计算结果,exceptionally 处理异常情况
whenComplete 和 whenCompleteAsync 的区别
whenComplete :是执行当前任务的线程继续执行 whencomplete 的任务
whenCompleteAsync: 是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行
方法不以 Async 结尾,意味着 Action 使用相同的线程执行,而 Async 可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)
4.3 handle 方法
和 complete 一样,可以对结果做最后的处理(可处理异常),可改变返回值
4.4 线程串行方法
thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任物的返回值
thenAccept方法:消费处理结果,接受任务处理结果,并消费处理,无返回结果
thenRun 方法:只要上面任务执行完成,就开始执行 thenRun ,只是处理完任务后,执行 thenRun的后续操作
4.5 任务组合-都要完成
两个任务必须都完成,触发该任务
thenCombine: 组合两个 future,获取两个 future的返回结果,并返回当前任务的返回值
thenAccpetBoth: 组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值
runAfterBoth:组合 两个 future,不需要获取 future 的结果,只需要两个 future处理完成任务后,处理该任务,
4.6 任务组合-一个完成
当两个任务中,任意一个future 任务完成时,执行任务
applyToEither;两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值
acceptEither: 两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值
runAfterEither:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返回值
4.7 多任务组合
allOf:等待所有任务完成
anyOf:只要有一个任务完成