学习线程以及线程池

53 阅读3分钟

线程池的优点

  • 减少资源的创建 => 减少了线程的创建
  • 降低系统的开销 => 省掉了创建线程的时间
  • 提高了稳定性 => 避免了创建太多的线程而导致OOM

Executors创建线程池的方式

  • 创建返回ThreadPoolExecutor对象
  • 创建返回ScheduleThreadPoolExecutor
  • 创建返回ForkJoinPool对象

Executor创建ThreadPoolExecutor对象

newCachedThreadPool方法 => 创建可缓存的线程池

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

队列永远是满的,而SynchronousQueue队列是不存储元素的队列,只进行转发和传送,所以最终都会创建非核心线程来执行任务。因为最大线程数设置的非常大可以认为可以无限创建线程,在资源有限的情况下会引起OOM

newSingleThreadExecutor方法 => 创建单线程的线程池

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
                            0L, TimeUnit.MILLISECONDS,
                            new LinkedBlockingQueue<Runnable>()));

}

当有任务提交时,能保障执行顺序就是提交顺序,会创建个核心线程来执行任务,当超过核心线程数量时,将会放入队列中,当核心线程执行任务异常时会调用那个非核心线程来执行。又因为LinkedBlockingQueue是长度为Max_value的队列,可以认为是无界队列,当往队列中插入无限多的任务时,在资源有限的情况下会发生OOM。并且因为无界队列可以认为与最大线程数相关的参数都无效maximumPoolSizekeepAliveTime(默认情况下只针对非核心线程)都会无效。

newFixedThreadPool方法 => 创建固定长度的线程池

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

当请求的任务数到达核心线程数的大小时,也不会再开新线程了因为最大线程数=核心线程数,与单线程线程池一样会产生无界队列的问题,唯一的区别就是核心线程数不同。

因为这三种创建ThreadPoolExecutor的方式都会造成OOM所以不建议使用Executor创建线程池,推荐自己去创建ThreadPoolExecutor,new 一个ThreadPoolExecutor(...)

正确的配置线程池 => 为了使程序的运行速度最大化

通过 相应的场景 + 正确的线程个数 => 运行速度 ,也就是充分利用CPU和I/O利用率

场景

  • CPU密集型程序 单核CPU下创建4个线程来处理1+2+....100亿 image.png 多核的情况下 image.png 单核CPU处理CPU密集型程序,这种情况不适合使用多线程。 多核CPU处理CPU密集型程序,此时可以最大化的利用CPU核心数,应用并发编程来提高效率。
  • I/O密集型程序 image.png 线程等待时间的占比越高,需要越多线程;线程CPU时间占比越高,需要越少线程。

创建多少个线程合适

  • CPU密集型程序创建线程数
    • 理论上线程数量 = CPU 核数(逻辑),但是实际上线程数量 = CPU核数 + 1多出来的一个线程是用来做兜底的,当出现问题时就会用到。
  • I/O密集型程序创建线程数
    • 单个CPU核心的最佳线程数 = (1/CPU利用率)= 1 + (I/O耗时/CPU耗时)
    • 多个CPU核心的最佳线程数 = CPU核心数 * (1/CPU利用率) = CPU * 1 + I/O耗时/CPU耗时
    • 假如全是I/O操作,那就设置为2N + 1(兜底的),N为CPU核数