线程的四种创建方式
1.继承Thread类
public static class Thread01 extends Thread {
@Override
public void run() {
System.out.println("执行线程-------------------------01");
}
}
Thread01 thread01 = new Thread01();
thread01.start();
2.实现Runnable接口
public static class Thread02 implements Runnable {
@Override
public void run() {
System.out.println("执行线程-------------------------02");
}
}
Thread thread02 = new Thread(new Thread02());
thread02.start();
3.实现Callable接口
public static class Thread03 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("执行线程-------------------------03");
return 3;
}
}
FutureFask futureFask = new FutureFask(new Thread03());
Thread thread03 = new Thread(futureFask);
thread03.start()
4.线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(thread02);
executorService.execute(futureTask);
线程池的7大参数
1.参数列表
-
corePoolSize核心线程数(一直存在),线程池创建好以后就准备就绪执行的数量
-
maximumPoolSize最大线程数量,控制资源
-
keepAliveTime存活时间,当前正在运行的线程数量>核心数量时,空闲线程等待的最大时间
释放空闲线程(maximumPoolSize - corePoolSize)
-
unit 时间单位
-
workQueue阻塞队列,如果任务很多,就会将目前多的任务放在队列里面。只要有线程空闲了,就会去队列里面取出新的任务执行。
-
threadFactory线程的创建工厂
-
handler处理队列满了的情况,就按照拒绝策略执行拒绝任务
2.工作顺序
-
线程池创建,准备好core数量的核心线程,准备接受任务
-
core满了,就将再进来的任务放入阻塞队列中,空闲的core就会自己去阻塞队列获取执行任务
-
阻塞队列满了,就直接开启新线程执行,最大只能开到max指定的数量
-
max满了就用拒绝策略,执行拒绝任务
-
max都执行完成,有很多空闲,在指定的存活时间以后,释放max-core这些线程
3.Executors类创建线程
Executors.newCachedThreadPool();
Executors.newFixedThreadPool();
如何正确配置线程池参数
1、首先为什么不推荐使用Executors类默认的创建线程池方法。
因为太耗费资源了,默认的创建参数不是coreSize太多,就是Queue太大,实际上很多资源都是空闲的。我们应该贴合自己的业务场景,创建合适大小的线程池。
2、当线程数过多时会增加上下文切换的成本。什么是上下文切换?
我们的CPU有一个核心个数(也就是我们俗称的几核CPU),任何一个CPU在同一时间只能在一个线程上面调度。比如说我们的餐馆点菜,10个桌子的客人(10个线程),只有7个服务员(7个CPU核心),那么必定有3个桌子是空闲的,服务员切换桌子的场景就是我们的CPU调度。切换的时候我们CPU会保存当前线程的一些状态信息,以便下次切换回来能够回到当前的执行状态,而线程数一但大了起来,我们的CPU会频繁的切换上下文,这其中需要耗费大量的时间,所以线程数不宜过大。
而线程数过小的话,会导致我们有大量的任务堆积到线程队列里面,甚至导致OOM,CPU根本没得到充分利用。
3、有一个简单并且适用面比较广的公式:
- CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。
- I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。