多线程之线程池

1.为什么使用线程池

通俗的讲,线程池就是一个线程集合,里面已经提前创建好了若干个线程,当需要线程的时候到线程集合里获取一个即可,这样省去了创建线程的时间,当然也省去了GC回收线程的时间,当线程池里的线程都被使用了后,只能阻塞等待了,等待获取线程池后被释放的线程,这样就不会无限制的去创建线程而导致Out of Memory

2.线程池使用

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 3L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20));
        for (int i = 0; i < 20; i++) {
            threadPoolExecutor.submit(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            });
        }
    }
}

3.ThreadPoolExecutor构造函数参数作用

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
                           
corePoolSize:核心线程数,线程池会维护线程的最少数量,默认情况下核心线程会一直存活,即使没有任务也不会受keepAliveTime控制
	坑:在刚创建线程池时线程不会立刻启动,到有任务提交时才开始创建线程并逐步线程数目达到corePoolSize
	
maximumPoolSize:线程池维护线程的最大数量,超过将被阻塞
	坑:当核心线程满,且阻塞队列也满时,才会判断当前线程数是否小于最大线程数,才决定是否创建新线程
	
keepAliveTime:非核心线程的闲置超时时间,超过这个时间就会被回收,直到线程数量等于corePoolSize

unit:指定keepAliveTime的单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS

workQueue:线程池中的任务队列,常用的是ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue

threadFactory:创建新线程时使用的工厂

handler:RejectedExecutionHandler是一个接口且只有一个方法,线程池中的数量大于maximumPoolSize,对拒绝任务的处理策略,默认有4种策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy
  • 运行机制,线程数个数,假设x是任务数
    • x <= 核心线程数,只启动x个线程
    • x > 核心线程数 && x < 任务队列 + 核心线程数,会启动核心线程数,其他的任务放到任务队列里
    • x > 核心线程数 && x > 任务队列 + 核心线程数
      • x - 任务队列 <= 最大数量,会启动(x - 任务队列)个线程
      • x - 任务队列 > 最大数量,会启动最大数量个线程来执行任务,其余任务执行相应的拒绝策略
  • 线程池拒绝策略
    • AbortPolicy:该策略直接抛出异常,阻止系统正常工作
    • CallerRunsPolicy:只要线程池没有关闭,该策略直接在调用者线程中执行当前被丢弃的任务
    • DiscardPolicy:直接丢弃任务
    • DiscardOldestPolicy:丢弃最老的一个请求(任务队列的第一个),再尝试提交任务

4.线程池使用建议

  • 尽量避免使用Executors创建线程池

    Executors创建线程池底层也是调用ThreadPoolExecutor,只不过使用不同的参数、队列、拒绝策略等,如果使用不当,会造成资源耗尽问题;直接使用ThreadPoolExecutor让使用者更加清楚线程池规则、常见参数的使用,避免风险

    常见的线程池问题:

    newFixedThreadPool和newSingleThreadExecutor:队列使用LinkedBlockingQueue,队列长度为Integer.MAX_VALUE,可能造成堆积导致OOM
    newScheduledThreadPool和newCachedThreadPool:线程池里面允许最大的线程数是Integer.MAX_VALUE,可能会创建多线程导致OOM

  • 创建线程池时,核心线程数不要过大

  • 相应的逻辑,发生异常时要处理

  • submit如果发生异常不会立即抛出,而是在get的时候再抛出异常

  • execute发生异常直接抛出异常

你可能感兴趣的:(多线程,java)