并发编程(十七):Executor和线程池

线程是一个重量级的对象,应该尽量避免频繁创建和销毁

1.线程池的设计思路

一般的池化资源的设计,都是涉及到两个办法:

class XXXPool{
  // 获取池化资源
  XXX acquire() {
  }
  // 释放池化资源
  void release(XXX x){
  }
}  

但是这种设计思路不适用于线程池,因为假如从池中拿到了一个Thread对象,但是Thread对象根本没有 execute(Runnable target) 这样的公共方法

因此,线程池采取的是一种生产者-消费者的模式:线程的使用方式生产者,线程池本身是消费者:

package main.com.lingwuee.zhang.JKChapter22;

import jdk.nashorn.internal.ir.Block;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;

/**
 * 简化的线程池模型
 * @Auther: zhang_yx
 * @Date: 2021/8/17 10:53
 */
public class MyThreadPool{

    private BlockingQueue<Runnable> workQueue;

    private List<WorkThread> threads = new ArrayList<>();

    public MyThreadPool(int poolSize, BlockingQueue workQueue) {
        this.workQueue = workQueue;
        for (int i = 0; i < poolSize; i++) {
            WorkThread workThread = new WorkThread();
            workThread.run();
            threads.add(workThread);
        }
    }

    public void executor(Runnable command){
        try {
            workQueue.put(command);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class WorkThread extends Thread{

        @Override
        public void run() {
            while (true){
                try {
                    Runnable task = workQueue.take();
                    task.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

2.如何使用Java中的线程池

ThreadPoolExecutor 的构造函数非常复杂,如下面代码所示,这个最完备的构造函数有 7 个参数。

ThreadPoolExecutor(
  int corePoolSize,
  int maximumPoolSize,
  long keepAliveTime,
  TimeUnit unit,
  BlockingQueue<Runnable> workQueue,
  ThreadFactory threadFactory,
  RejectedExecutionHandler handler) 

可以将线程池比作一个项目组,而线程比较组内的组员,使用者只需要将项目丢到项目组中交由员工执行

  1. corePoolSize:核心线程数,表示线程池保有的最小线程数。那么没有任务,也会维持这些线程,JDK1.6新增的allowCoreThreadTimeOut(boolean value) ,可以让核心线程数超过keepAliveTime & unit的时间也被回收
  2. maximumPoolSize:表示线程池创建的最大线程数,当工作队列已经满了的时候,会新增线程到maximumPoolSize,如果此时队列满了且线程数达到了maximumPoolSize,此时就会采取拒绝策略
  3. keepAliveTime & unit:当线程数量大于核心线程数时候,并线程空闲时间大于keepAliveTime & unit则会回收多余线程
  4. workQueue:工作队列,存储待执行的任务
  5. threadFactory:自定义如何创建线程,例如给线程指定一个名称等
  6. handler:自定义拒绝策略,当队列满+线程数达到了最大线程数,此时新来的任务就会拒绝,ThreadpoolExecutor已经提供了下面四种策略:
    • CallerRunsPolicy:提交任务的线程自己去执行任务
    • AbortPolicy:默认拒绝策略,会 throws RejectedExecutionException
    • DiscardPolicy:直接丢弃任务,没有任何异常抛出
    • DiscardOldestPolicy:丢弃最老的任务,其实就是将最早进入队列的任务丢弃,然后将新任务加入队列

3.使用线程池的注意点

  1. Java 并发包里提供了一个线程池的静态工厂类 Executors,利用 Executors 你可以快速创建线程池,但是不建议使用。原因是Executors 提供的很多方法默认使用的是无界的LinkedBlockingQueue,高负载情况下,无界队列很容易产生OOM,因此强烈建议使用有界队列
  2. 使用有界队列,可能会触发默认拒绝策略,从而throws RejectExecutionException,该异常是允许时异常,因此需要注意catch并处理

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×