一. 为什么要学多线程
- 应付面试 :多线程几乎是面试中必问的题,所以掌握一定的基础知识是必须的。
- 了解并发编程:实际工作中很少写多线程的代码,这部分代码一般都被人封装起来了,在业务中使用多线程的机会也不是很多(看具体项目),虽然代码中很少会自己去创建线程,但是实际环境中每行代码却都是并行执行的,同一时刻大量请求同一个接口,并发可能会产生一些问题,所以也需要掌握一定的并发知识。
二、进程与线程
- 线程与进程的关系:
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程
比如说最常见的电脑管家,一个正在运行的软件(如电脑管家)就是一个进程,一个进程可以同时运行多个任务(我同时可以杀毒、清理垃圾、下载软件等等)这样的每个任务就是一个线程,我们可以简单的认为进程是线程的集合
三、单线程与多线程
上述我们讲到的电脑管家就是一个多线程,我可以同时执行多个任务。
优缺点:
- 多线程可以提高程序的效率,提高用户体验。
- 但同时增加的系统,资源压力 多线程情况下的共享资源问题,线程冲突,造成线程安全问题
四、多线程创建方式
- 继承 Thread
- 实现 Runable
- 实现 Callable
五、线程安全问题
六、线程池
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗
参数名 | 作用 |
---|---|
corePoolSize | 核心线程数 |
maximumPoolSize 名 | 最大线程池大小 |
keepAliveTime | 线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间 |
TimeUnit | keepAliveTime时间单位 |
workQueue | 阻塞任务队列 用来保存等待被执行任务的阻塞队列。常用的有:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue和PriorityBlockingQueue等 |
threadFactory | 新建线程工厂 |
RejectedExecutionHandler | 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理 |
重点讲解:
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
ThreadPoolExecutor的大致运行过程如下:
如果使用的是有界阻塞队列:
有新的任务需要执行,并且当前线程池的线程数小于核心线程数,则创建一个核心线程来执行。如果当前线程数大于核心线程数,则会将除了核心线程处理的任务之外剩下的任务加入到阻塞队列中等待执行。如果队列已满,则在当前线程数不大于最大线程数的前提下,创建新的非核心线程,处理完毕后等到达keepAliveTime空闲时间后会被直接销毁
(注意,不一定销毁的就是这些非核心线程,核心线程也可能被销毁,只要减到剩余线程数到达核心线程数就行。核心线程和非核心线程的区别仅在于判断是否到达阈值时有区别:核心线程判断的是核心线程数,而非核心线程判断的是最大线程数。仅此一个区别。后面讲源码时会再强调这一点)
。如果当前线程数大于最大线程数,则会执行相应的拒绝策略。如果使用的是无界阻塞队列:
与有界阻塞队列相比,除非系统资源耗尽,否则无界的阻塞队列不存在任务入队失败的情况。当有新任务到来,系统的线程数小于核心线程数时,则创建一个核心线程来执行。当达到核心线程数后,就不会继续增加。若后续仍有新的任务加入,而没有空闲的线程资源,则任务直接进入阻塞队列中进行等待。如果任务创建和处理任务的速度差异很大,无界阻塞队列会保持快速增长,直到耗尽系统内存。
定制属于自己的线程池
package mn.xzw.cn
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class ThreadPoolService {
private static ThreadPoolExecutor executor;
public ThreadPoolService() {
}
@PostConstruct
public void init() {
log.info("-------------------------初始化线程池---------------------------");
//核心线程数,最大线程数,线程空闲时间,毫秒
executor = new ThreadPoolExecutor(20, 30, 20000, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>());
}
public ThreadPoolExecutor getExecutor() {
return executor;
}
}
handler:如果当前阻塞队列已满,并且当前的线程数量已超过了最大线程数,则会执行相应的拒绝策略。具体有四种(也可以自己实现):
AbortPolicy:默认实现,会直接抛出RejectedExecutionException;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardPolicy:直接抛弃,任务不执行;
DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务。
核心线程数——>队列——>最大线程数
当提最大线程数满了 任务会交给RejectedExecutionHandler来处理
- LinkedBlockingQueue
它如果不指定容量,默认为Integer.MAX_VALUE,也就是无界队列,需要考虑任务量过大,造成OOM(出现机器负载或者内存爆满的情况)