a.假如线程创建的时间是time1,线程执行的时间是time2,线程销毁的时间呢是time3,往往time1+time3>time2,所以频繁的创建线程,会消耗额外的时间
b.如果等到有任务来了,在去创建线程的话效率就会比较低,如不把线程放在某个地方,任务来了,直接把线程拿过来用比较好
c.线程池可以管理控制线程,线程是稀缺资源,如果不停地创建线程会消耗大量的系统资源,还会降低系统的稳定性。使用线程池,可以进行统一分配,方便调优和监控
d.线程可以提供队列,存放缓冲等待执行的任务
普通线程启动方式
/**
* 普通处理线程的方法
*/
@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个线程在跑,剩下的也都在等待该这是个线程跑完,才会有新的线程执行
简单线程池设计思路
要有阻塞队列丶执行器丶线程池子(存放线程)
从下面这张图我们可以看出:
1.提交任务
2.判断线程数是否达到最大(是则进入判断任务队列)否就提交任务
3.判断任务队列是否已满(是则进入判断最大线程数)否就将任务加在任务队列
4.判断线程达到最大线程数(是则进入饱和策略)否则创建非核心线程执行任务
5.执行饱和策略,根据饱和策略的不同,程序作出不同的响应
在我们的修复方案中,选择的就是这个类型的队列,虽然会有部分任务被丢失,但是我们线上是排序日志搜集任务,所以对部分对丢失是可以容忍的。
AbortPolicy终止策略(默认)
DiscardPolicy抛弃策略
DiscardOldestPolicy抛弃旧任务策略
CallerRunsPolicy调用者运行策略
newCachedThreadPool线程数量无限线程池
newFixedThreadPool线程数量固定线程池
newSingleThreadExecutor单一线程线程池
五种状态