文章目录
a.假如线程创建的时间是time1,线程执行的时间是time2,线程销毁的时间呢是time3,往往time1+time3>time2,所以频繁的创建线程,会消耗额外的时间
b.如果等到有任务来了,在去创建线程的话效率就会比较低,如不把线程放在某个地方,任务来了,直接把线程拿过来用比较好
c.线程池可以管理控制线程,线程是稀缺资源,如果不停地创建线程会消耗大量的系统资源,还会降低系统的稳定性。使用线程池,可以进行统一分配,方便调优和监控
d.线程可以提供队列,存放缓冲等待执行的任务
2 效果对比
- 下面我们来模拟一下 100个用户同时访问web端 执行一个操作
普通线程启动方式
/**
* 普通处理线程的方法
*/
@Test
public void oldMethod() throws InterruptedException {
//书写一个循环 模拟100个用户同时访问
for (int request = 0; request< 100;request ++){
//开启一个线程
new Thread(()->{
System.out.println("开始执行...");
/**
* 睡10秒
*/
try {
Thread.sleep(1000L * 30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束执行...");
}).start();
}
//为了方便测试 我们让主线程睡一会
Thread.sleep(1000 * 1000);
}
这时我们看下下面的图片的,一共有100个线程在执行,如果要是10000个用户来访问该程序,系统不得直接给干崩溃了!所以我们就得使用下面的线程池
利用线程池启动线程方式
@Test
public void newMethod() throws InterruptedException {
/**
* 开启一个线程池 默认线程是10个
* 使用默认线程工厂
* 拒绝策略为CallerRunsPolicy策略,让后面的线程先等待
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());
//书写一个循环 模拟100个用户同时访问
for (int request = 0; request< 100;request ++){
//开启一个线程
threadPoolExecutor.execute(() ->{
System.out.println("开始执行...");
/**
* 睡10秒
*/
try {
Thread.sleep(1000L * 30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束执行...");
});
}
//为了方便测试 我们让主线程睡一会
Thread.sleep(1000 * 1000);
}
我们可以看到这时候只有10个线程在跑,剩下的也都在等待该这是个线程跑完,才会有新的线程执行
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
简单线程池设计思路
要有阻塞队列丶执行器丶线程池子(存放线程)
- 设计过程中要思考的问题
- 初始创建多少线程?
- 没有可用线程了怎么办?
- 缓冲数组要多长?
- 缓冲数组满了怎么办?
- corePoolSize 核心池大小
- maximumPoolSize 池中允许的最大线程,这个参数表示了线程池中最多的线程数量
- keepAliveTime 当线程大于corePoolSize时,终止前多余的空闲线程等待新任务的最长时间
- unit 上一个参数的时间单位
- workQueue 存储还没来的及执行的任务
- threadFactory 执行程序创建新线程时使用的工厂
- handler 由于超出线程范围和队列容量而使执行被阻塞时使用的处理程序
从下面这张图我们可以看出:
1.提交任务
2.判断线程数是否达到最大(是则进入判断任务队列)否就提交任务
3.判断任务队列是否已满(是则进入判断最大线程数)否就将任务加在任务队列
4.判断线程达到最大线程数(是则进入饱和策略)否则创建非核心线程执行任务
5.执行饱和策略,根据饱和策略的不同,程序作出不同的响应
- 无界队列(常用的为无界的LinkedBlockingQueue)
可以无限创建线程(有风险) - 有界队列
常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
在我们的修复方案中,选择的就是这个类型的队列,虽然会有部分任务被丢失,但是我们线上是排序日志搜集任务,所以对部分对丢失是可以容忍的。
- 同步移交队列
如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。
AbortPolicy终止策略(默认)
DiscardPolicy抛弃策略
DiscardOldestPolicy抛弃旧任务策略
CallerRunsPolicy调用者运行策略
9 线程池的执行示意图
10 常用线程池
newCachedThreadPool线程数量无限线程池
newFixedThreadPool线程数量固定线程池
newSingleThreadExecutor单一线程线程池
11 线程池的状态
五种状态
- RUNNING
- SHUTDOWN
- STOP
- TIDYING
- TERMINATED