一:线程基础

线程与进程:进程是用来获取资源的单位,线程是用来调配资源的单位,一个进程可能有多个线程运行
多线程是为了提高程序运行效率.

线程生命周期

  • NEW - 初始状态,一个新创建的线程,还没开始执行。
  • RUNNABLE - 可执行的状态,要么是在执行,要么是一切就绪等待执行,例如等待分配CPU时间。
  • WAITING - 等待状态,等待其他的线程去执行特定的动作,没有时间限制。
  • TIMED_WAITING - 限时等待状态,等待其他的线程去执行特定的动作,这个是在一个指定的时间范围内。
  • BLOCKED - 阻塞状态,等待锁,以便进入同步块儿。
  • TERMINATED - 终止状态,线程执行结束。

    线程问题:

    线程安全问题

    线程安全问题指的是,内存问题, 线程存在公共区域的数据被其他线程修改,导致数据存在安全问题.
    解决措施:
  1. 加锁—保证同时只有一个人访问公共数据 —同步阻塞
  2. cas 操作,,但会有aba问题,数据被修改不知道—非同步阻塞
  3. 无同步方案,将线程限制在个线程范围内,这样无需同步也能保证数据不被征用.—ThreadLocal

使用多线程可能会有以下问题:
内存泄漏 上下文切换 死锁

上下文切换

一般程序设定的线程数量多于cpu核心数量,cpu采取的是分片方式执行线程,线程间切换就是上下文切换

死锁

多个线程抢占资源: 当前线程拥有其他线程需要的资源,当前线程等待其他线程已拥有的资 源,都不放弃⾃⼰拥有的资源。

解决方式

  1. 固定加锁的顺序,⽐如我们可以使⽤Hash值的⼤⼩来确定加锁的先后
  2. 尽可能缩减加锁的范围,等到操作共享变量的时候才加锁。
  3. 使⽤可释放的定时锁(⼀段时间申请不到锁的权限了,直接释放掉

开发中线程安全思考

image.png

  1. 能不能保证操作的原⼦性,考虑atomic包下的类够不够我们使⽤。
  2. 能不能保证操作的可⻅性,考虑volatile关键字够不够我们使⽤
  3. 如果涉及到对线程的控制(⽐如⼀次能使⽤多少个线程,当前线程触发的条件是否依赖其他线程的结果), 考虑CountDownLatch/Semaphore等等。
  4. 如果是集合,考虑java.util.concurrent包下的集合类。
  5. 如果synchronized⽆法满⾜,考虑lock包下的类

    1. <br /> <br /> <br /> <br />

    synchronized 关键字和 volatile 关键字的区别 synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!

volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定比 synchronized 关键字 要好。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码 块
volatile 关键字能保证数据的可⻅性,但不能保证数据的原子性。 synchronized 关键字两 者都能保证。
volatile 关键字主要用于解决变量在多个线程之间的可⻅性,而 synchronized 关键字解决 的是多个线程之间访问资源的同步性。



常见实现多线程的方式

区别: Thrad 类不适合资源的共享, Runnable接口适合资源共享

  1. 继承Thrad 类
  2. 实现Runnable接口
  3. 有返回值的线程操作,实现Callable 接口,搭配线程池使用.返回Future对象

二:线程池

JVM在HotSpot的线程模型下,Java线程会⼀对⼀映射为内核线程 ,线程的创建与销毁会浪费大量时间,线程池为提高线程的复用性,以及固定线程的数量

ThreadPoolExecutor 相比 Executors 优点,更能了解线程池的运行规则,避免资源损耗

image.png

参数名 参数内容
corePoolSize 核心线程数量
maximumPoolSize 最高线程数量
keepAliveTime 当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最长时间
unit keepAliveTime参数的时间单位
workQueue 用于在执行任务之前保存任务的队列。此队列将仅保存由execute方法提交的Runnable任务。
threadFactory 执行器创建新线程时使用的工厂
handler 由于达到线程边界和队列容量而阻塞执行时使用的处理程序

三: ThreadLocal 线程共享变量

数据结构
image.png

  • 什么是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 提供了四个静态方法来创建一个异步操作
image.png
1、runXxx 都是没有返回结果的,supplyXxxx都是可以获取返回结果的
2、可以传入自定义的线程池,否则就是用默认的线程池
3、根据方法的返回类型来判断是否该方法是否有返回类型

4.2计算完成时回调方法

image.png
whenComplete 可以处理正常和异常的计算结果,exceptionally 处理异常情况
whenComplete 和 whenCompleteAsync 的区别
whenComplete :是执行当前任务的线程继续执行 whencomplete 的任务
whenCompleteAsync: 是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行
方法不以 Async 结尾,意味着 Action 使用相同的线程执行,而 Async 可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)

4.3 handle 方法

和 complete 一样,可以对结果做最后的处理(可处理异常),可改变返回值
image.png

4.4 线程串行方法
image.png

thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任物的返回值
thenAccept方法:消费处理结果,接受任务处理结果,并消费处理,无返回结果
thenRun 方法:只要上面任务执行完成,就开始执行 thenRun ,只是处理完任务后,执行 thenRun的后续操作

4.5 任务组合-都要完成

image.png
两个任务必须都完成,触发该任务
thenCombine: 组合两个 future,获取两个 future的返回结果,并返回当前任务的返回值
thenAccpetBoth: 组合两个 future,获取两个 future 任务的返回结果,然后处理任务,没有返回值
runAfterBoth:组合 两个 future,不需要获取 future 的结果,只需要两个 future处理完成任务后,处理该任务,

4.6 任务组合-一个完成

image.png

当两个任务中,任意一个future 任务完成时,执行任务

applyToEither;两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值
acceptEither: 两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值
runAfterEither:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返回值

4.7 多任务组合

image.png
allOf:等待所有任务完成
anyOf:只要有一个任务完成