[关闭]
@yudesong 2018-02-22T14:04:57.000000Z 字数 4980 阅读 649

线程池原理

线程池


new Thread 的弊端:

线程池的好处:

Java 线程池种类

JDK 为我们内置了4种常见线程池的实现,均可以使用 Executors 工厂类创建。

为了错误避免创建过多线程导致系统奔溃,建议使用有界队列。因为它在无法添加更多任务时会拒绝任务,这样可以提前预警,避免影响整个系统。

执行时间、顺序有要求的话可以选择优先级队列,同时也要保证低优先级的任务有机会被执行。

创建线程池需要使用ThreadPoolExecutor

  1. public ThreadPoolExecutor(int corePoolSize, //核心线程的数量
  2. int maximumPoolSize, //最大线程数量
  3. long keepAliveTime, //超出核心线程数量以外的线程空余存活时间
  4. TimeUnit unit, //存活时间的单位
  5. BlockingQueue<Runnable> workQueue, //保存待执行任务的队列
  6. ThreadFactory threadFactory, //创建新线程使用的工厂
  7. RejectedExecutionHandler handler // 当任务无法执行时的处理器
  8. ) {...}

其核心的方法execute

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4. int c = ctl.get();
  5. //1.当前池中线程比核心数少,新建一个线程执行任务
  6. if (workerCountOf(c) < corePoolSize) {
  7. if (addWorker(command, true))
  8. return;
  9. c = ctl.get();
  10. }
  11. //2.核心池已满,但任务队列未满,添加到队列中
  12. if (isRunning(c) && workQueue.offer(command)) {
  13. int recheck = ctl.get();
  14. if (! isRunning(recheck) && remove(command))
  15. //如果这时被关闭了,拒绝任务
  16. reject(command);
  17. else if (workerCountOf(recheck) == 0)
  18. //如果之前的线程已被销毁完,新建一个线程
  19. addWorker(null, false);
  20. }
  21. //3.核心池已满,队列已满,试着创建一个新线程
  22. else if (!addWorker(command, false))
  23. reject(command);
  24. //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
  25. }

以下线程池的主要工作流程:

由于 1 和 3 新建线程时需要获取全局锁,这将严重影响性能。因此 ThreadPoolExecutor 这样的处理流程是为了在执行 execute() 方法时尽量少地执行 1 和 3,多执行 2。

在 ThreadPoolExecutor 完成预热后(当前线程数不少于核心线程数),几乎所有的 execute() 都是在执行步骤 2。

前面提到的 ThreadPoolExecutor 构造函数的参数,分别影响以下内容:

保存待执行任务的阻塞队列

当线程池中的核心线程数已满时,任务就要保存到队列中了。

线程池中使用的队列是 BlockingQueue 接口,常用的实现有如下几种:

自定义线程池

  1. public class ThreadPoolManager {
  2. private final String TAG = this.getClass().getSimpleName();
  3. private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;
  4. // 核心线程数为 CPU数*2
  5. private static final int MAXIMUM_POOL_SIZE = 64;
  6. // 线程队列最大线程数
  7. private static final int KEEP_ALIVE_TIME = 1;
  8. // 保持存活时间 1秒
  9. /*
  10. ** 如果是要求高吞吐量的,可以使用 SynchronousQueue 队列;如果对执行顺序有要求,可以使用 PriorityBlockingQueue;如果最大积攒的待做任务有上限,可以使用 LinkedBlockingQueue
  11. */
  12. private final BlockingQueue<Runnable> mWorkQueue = new LinkedBlockingQueue<>(128);
  13. private final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() {
  14. private final AtomicInteger mCount = new AtomicInteger(1);
  15. public Thread newThread(Runnable r) {
  16. Thread thread = new Thread(r, TAG + " #" + mCount.getAndIncrement());
  17. thread.setPriority(Thread.NORM_PRIORITY);
  18. return thread;
  19. }
  20. };
  21. private ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME,
  22. TimeUnit.SECONDS, mWorkQueue, DEFAULT_THREAD_FACTORY,
  23. new ThreadPoolExecutor.DiscardOldestPolicy());
  24. private static volatile ThreadPoolManager mInstance = new ThreadPoolManager();
  25. public static ThreadPoolManager getInstance() {
  26. return mInstance;
  27. }
  28. public void addTask(Runnable runnable) {
  29. mExecutor.execute(runnable);
  30. }
  31. @Deprecated
  32. public void shutdownNow() {
  33. mExecutor.shutdownNow();
  34. }
  35. }

两种提交任务的方法

ExecutorService 提供了两种提交任务的方法:

  1. <T> Future<T> submit(Callable<T> task);
  2. <T> Future<T> submit(Runnable task, T result);
  3. Future<?> submit(Runnable task);

submit() 有三种重载,参数可以是 Callable 也可以是 Runnable。
同时它会返回一个 Funture 对象,通过它我们可以判断任务是否执行成功。
获得执行结果调用 Future.get()方法,这个方法会阻塞当前线程直到任务完成。
提交一个 Callable 任务时,需要使用 FutureTask 包一层

  1. FutureTask futureTask = new FutureTask(new Callable<String>() { //创建 Callable 任务
  2. @Override
  3. public String call() throws Exception {
  4. String result = "";
  5. //do something
  6. return result;
  7. }
  8. });
  9. Future<?> submit = executor.submit(futureTask); //提交到线程池
  10. try {
  11. Object result = submit.get(); //获取结果
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. } catch (ExecutionException e) {
  15. e.printStackTrace();
  16. }

关闭线程池

线程池即使不执行任务也会占用一些资源,所以在我们要退出任务时最好关闭线程池。

有两个方法关闭线程池:

它们的共同点是:都是通过遍历线程池中的工作线程,逐个调用 Thread.interrup()来中断线程,所以一些无法响应中断的任务可能永远无法停止(比如 Runnable)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注