线程池

319 阅读12分钟

什么是线程池?为什么要用线程池

  • 降低资源的消耗 ,降低创建和销毁线程的资源消耗
  • 提高响应速度,线程的创建时间为T1,执行时间为T2,销毁时间为T3,免去了T1和图T3的时间
  • 提高了线程的额可管理性

实现一个自己的线程

  • 线程必须在池中就创建好,并且可以保持住,要有容器保存多个线程
  • 线程还要能够接受外部任务,运行这个任务,容器还得保存来不及运行的任务

实现代码如下

    //线程池中的默认线程个数
    private static int WORK_NUM = 5;
    //默认的任务个数为100
    private static int TASK_COUNT = 100;

    //工作线程组
    private WorkerThread[] workerThreads;

    //任务队列
    private final BlockingQueue<Runnable> taskQueue;

    //工作线程
    private int workNum;


    public MyThreadPool() {
        this(WORK_NUM, TASK_COUNT);
    }

    public MyThreadPool(int workNum, int taskCount) {
        if (workNum <= 0) workNum = WORK_NUM;
        if (taskCount <= 0) taskCount = TASK_COUNT;
        this.workNum = workNum;
        taskQueue = new ArrayBlockingQueue<Runnable>(taskCount);
        workerThreads = new WorkerThread[workNum];
        for (int i = 0; i < workNum; i++) {
            workerThreads[i] = new WorkerThread();
            workerThreads[i].start();
        }
        System.out.println(Runtime.getRuntime().availableProcessors());

    }

    public void execute(Runnable task) {
        try {
            taskQueue.put(task);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void destroy() {
        System.out.println("pool will destroy");
        for (int i = 0; i < workNum; i++) {
            workerThreads[i].stopWorker();
            workerThreads[i] = null;//help gc
        }
        taskQueue.clear();
    }

    @Override
    public String toString() {
        return "WorkThread number:" + workNum
                + "  wait task number:" + taskQueue.size();
    }

    private class WorkerThread extends Thread {
        @Override
        public void run() {
            try {
                Runnable r = null;
                while (!isInterrupted()) {
                    r = taskQueue.take();
                    if (null != r) {
                        System.out.println(getId() + " ready exec " + r);
                        r.run();
                    }
                    r = null;//help GC
                }

            } catch (InterruptedException e) {
                //处理异常
            }

        }

        public void stopWorker() {
            interrupt();
        }
    }
}

测试线程池的类

public class TestMyThreadPool {

    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool(3, 0);

        myThreadPool.execute(new MyTask("Test_A"));
        myThreadPool.execute(new MyTask("Test_B"));
        myThreadPool.execute(new MyTask("Test_C"));
        myThreadPool.execute(new MyTask("Test_D"));
        myThreadPool.execute(new MyTask("Test_E"));

        System.out.println(myThreadPool);
        Thread.sleep(10000);
        myThreadPool.destroy();
        System.out.println(myThreadPool);
    }

    static class MyTask implements Runnable {

        private String name;
        private Random random = new Random();

        public MyTask(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(2000 + random.nextInt(1000));
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getId() + " sleep InterruptedException " + Thread.currentThread().isInterrupted());
            }
            System.out.println("任务" + name + "完成");
        }
    }
}

输出如下

WorkThread number:3  wait task number:4
11 ready exec :com.xiangxue.ch6.mypool.TestMyThreadPool$MyTask@70912299
12 ready exec :com.xiangxue.ch6.mypool.TestMyThreadPool$MyTask@5fa0b406
13 ready exec :com.xiangxue.ch6.mypool.TestMyThreadPool$MyTask@21489687
任务 testC 完成
13 ready exec :com.xiangxue.ch6.mypool.TestMyThreadPool$MyTask@2f8f6cde
任务 testB 完成
12 ready exec :com.xiangxue.ch6.mypool.TestMyThreadPool$MyTask@51675182
任务 testA 完成
任务 testD 完成
任务 testE 完成
ready close pool.....
WorkThread number:3  wait task number:0

JDK中线程池的工作机制

线程池的创建

ThreadPoolExecutor,JDK中所有线程池类的父类

各个参数
  • int corePoolSize 核心线程的数量,当前池中的线程数n,若n<corePoolSize,在池中创建新的线程,若n>=corePoolSize,则需要吧接下来的任务放入阻塞队列workQueue。如果调用prestartAllCoreThreads()则会一次性创建线程知道n = corePoolSize
  • int maximumPoolSize 允许的最大线程数,当 workQueue队列满了,且corePoolSize<n<maxmumPoolSize时会再次创建线程
  • ong keepAliveTime 线程空闲下来之后存活的时间,这个参数只有在n>maxmumPoolSize时有作用
  • TimeUnit unit 存活时间单位
  • BlockingQueue workQueue 保存任务的阻塞队列
  • ThreadFactory threadFactory 创建线程的工厂,主要用于给线程赋予名字
  • RejectedExecutionHandler handler 饱和策略
    • AbortPolicy 默认策略,直接抛出异常
    • CallerRunsPolicy 由调用者所在的线程来执行任务
    • DiscardOldestPolicy 丢弃阻塞队列里面最老的任务,也就是最靠前的任务
    • DiscaardPolicy 直接丢弃任务
    • 实现自己的饱和策略,实现RejectExcutionHandler接口即可
/**
 * Creates a new {@code ThreadPoolExecutor} with the given initial
 * parameters.
 *
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @param maximumPoolSize the maximum number of threads to allow in the
 *        pool
 * @param keepAliveTime when the number of threads is greater than
 *        the core, this is the maximum time that excess idle threads
 *        will wait for new tasks before terminating.
 * @param unit the time unit for the {@code keepAliveTime} argument
 * @param workQueue the queue to use for holding tasks before they are
 *        executed.  This queue will hold only the {@code Runnable}
 *        tasks submitted by the {@code execute} method.
 * @param threadFactory the factory to use when the executor
 *        creates a new thread
 * @param handler the handler to use when execution is blocked
 *        because the thread bounds and queue capacities are reached
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue}
 *         or {@code threadFactory} or {@code handler} is null
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
提交任务
  • execute(Runnable command) 不需要返回
  • submit(Callable task) 需要返回
关闭线程池
  • shutdown 设置线程池的状态,你会中断没有执行的线程
  • shutdownNow 设置线程池的状态,不仅中断没有执行的线程,还会尝试停止正在运行或者暂停的线程
工作机制
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
  //1.若线程数量小于corePoolSize,直接创建线程
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
  //2.若线程数>corePoolSize,放入队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
  //3.重新创建新线程,(c<maxmumPoolSize)
  //4.若失败之间调用饱和策略
    else if (!addWorker(command, false))
        reject(command);
}

详情如下图:

image-20200422125706450
如何使用线程池
public class UseThreadPool {

    static class Worker implements Runnable {

        private String taskName;

        private Random random = new Random();

        public Worker(String taskName) {
            this.taskName = taskName;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " process the task " + taskName);
            SleepTools.ms(random.nextInt(100) * 5);
        }
    }


    static class CallWorker implements Callable<Integer> {

        private String taskName;

        private Random random = new Random();

        public CallWorker(String taskName) {
            this.taskName = taskName;
        }

        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName() + " process the task " + taskName);
            int i = random.nextInt(100) * 5;
            SleepTools.ms(i);
            return i;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4, 3,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(6), new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 6; i++) {
            pool.execute(new Worker("Worker_" + i));
        }
        for (int i = 0; i < 6; i++) {
            Future<Integer> submit = pool.submit(new CallWorker("Worker_" + i));
            System.out.println(submit.get());

        }
        pool.shutdown();

    }
}

输出如下

pool-1-thread-1 process the task Worker_0
pool-1-thread-2 process the task Worker_1
pool-1-thread-1 process the task Worker_2
pool-1-thread-2 process the task Worker_3
pool-1-thread-1 process the task Worker_4
pool-1-thread-2 process the task Worker_5
pool-1-thread-1 process the task Worker_0
490
pool-1-thread-2 process the task Worker_1
35
pool-1-thread-1 process the task Worker_2
440
pool-1-thread-2 process the task Worker_3
50
pool-1-thread-1 process the task Worker_4
210
pool-1-thread-2 process the task Worker_5
470

合理的分配线程池

按照任务的性质来,我们可以将任务分为「计算(cpu)密集型」,「IO密集型」,「混合型」

  • 计算密集型:如加密,大数分解,正则....线程数适当小一些,最大推荐:机器的cpu核心数(Runtime.getRuntime().availaleProcessrs())+1(防止页缺失),
  • IO密集型:读取文件,数据库连接,网络通信,线程数适当大一些,机器的cpu核心数*2
  • 混合型:尽量拆分,IO密集型>>计算密集型,拆封意义不大,

队列选择上尽量选择有界队列,无界队列可能会导致内存溢出oom

JDK预定义的线程池

  • FixedThreadPool 创建固定线程数量的线程池,适用于负载中的服务器,使用的是无界队列

        public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    
  • SingleThreadExecutor 创建单个线程的线程池,需要顺序保证任务执行,不会有多个线程活动,适用的是无界队列

     public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    
  • CachedThreadPool 会根据需要来创建新线程,适用于执行很多短期一步任务程序,阻塞队列适用的是SynChronousQueue

      public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    
  • WorkStealingPool 工作密取,forkjoin

    public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    
  • ScheduledThreadPoolExecutor 需要定期执行周期任务的线程池,不建议使用Timer,主要有以下三个线程池

    • newSingleScheduledThreadExecutor 只包含一个线程,用于单线程的周期执行,保证执行顺序

      public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
          return new DelegatedScheduledExecutorService
              (new ScheduledThreadPoolExecutor(1, threadFactory));
      }
      
    • newScheduledThreadPool 可以包含多闲程序的,执行周期任务,主要有以下三个方法

      public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
          return new ScheduledThreadPoolExecutor(corePoolSize);
      }
      
      • schedule:只执行一次

      • scheduledAtFixedRate:提叫固定时间间隔的任务

      • scheduledWithFixDelay:提交固定时延间隔的任务

      • scheduleAtFixedRate与scheduleWithFixDelay的区别

        image-20200422132425918
      • scheduleAtFiexRate存在一个超时的情况如下

        规定任务每60s执行一次,有任务执行了80s,下个任务马上执行

        第一个任务80s,第二个任务20s,第三个任务50s

        第一个任务有0s开始,80s结束

        第二个任务与80s开始,100s结束

        第三个任务有120s开始,170s借宿

        第四个任务由180s开始

    相关的使用代码如下

    • 如何使用:

      编写工作类

      public class ScheduledWorker implements Runnable {
      
          public static final int NORMAL = 0; //普通任务类型
          public static final int HAS_EXCEPTION = -1;//抛出异常的任务类型
          public static final int PROCESS_EXCEPTION = 1;//抛出异常但会捕捉的类型
      
      
          //多线程下并不安全SimpleDateFormat,所以使用ThreadLocal对象
          ThreadLocal<SimpleDateFormat> formatThreadLocal =
            new ThreadLocal<SimpleDateFormat>() {
              @Override
              protected SimpleDateFormat initialValue() {
                  return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
              }
          };
      
          private int taskType;
      
      
          public ScheduledWorker(int taskType) {
              this.taskType = taskType;
          }
      
          @Override
          public void run() {
              if (HAS_EXCEPTION == taskType) {
                  System.out.println(formatThreadLocal.get().format(new Date()) 
                                     + " Exception made ......");
                  throw new RuntimeException("Exception happened");
              } else if (PROCESS_EXCEPTION == taskType) {
                  try {
                      System.out.println(formatThreadLocal.get().format(new Date())
                                         + " Exception made ,but catch");
                      throw new RuntimeException("Exception happened");
                  } catch (Exception e) {
                      System.out.println(" Exception be catch");
                  }
              } else {
                  System.out.println("Normal......" + formatThreadLocal.get().format(new Date()));
              }
          }
      }
      

      编写测试类

      public class ScheduledCase {
          public static void main(String[] args){
              ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(3);
      
              pool.scheduleAtFixedRate(
                new ScheduledWorker(ScheduledWorker.NORMAL),
                      1000,3000, TimeUnit.MILLISECONDS);
              pool.scheduleAtFixedRate(
                new ScheduledWorker(ScheduledWorker.HAS_EXCEPTION),
                      1000,3000, TimeUnit.MILLISECONDS);
              pool.scheduleAtFixedRate(
                new ScheduledWorker(ScheduledWorker.PROCESS_EXCEPTION),
                      1000,3000, TimeUnit.MILLISECONDS);
          }
      
      }
      

      输出如下

      2020-04-22 14:46:10 Exception made ......
      Normal......2020-04-22 14:46:10
      2020-04-22 14:46:10 Exception made ,but catch
       Exception be catch
      Normal......2020-04-22 14:46:13
      2020-04-22 14:46:13 Exception made ,but catch
       Exception be catch
      Normal......2020-04-22 14:46:16
      2020-04-22 14:46:16 Exception made ,but catch
       Exception be catch
      Normal......2020-04-22 14:46:19
      2020-04-22 14:46:19 Exception made ,but catch
       Exception be catch
      Normal......2020-04-22 14:46:22
      2020-04-22 14:46:22 Exception made ,but catch
       Exception be catch
      

      由此可以看出在把任务提交给ScheduledThreadPoolExecutor时,要捕捉异常,不然一旦任务抛出异常,任务就不再执行,想HAS_EXCEPTION的情况一样

    • 演示scheduleAtFiexRate超时

      public class ScheduledThreadWorkTime implements Runnable {
      
          private static final int Long = 8;
          private static final int Short = 2;
          private static final int Normal = 5;
      
      
          private static ThreadLocal<SimpleDateFormat> format =
                  new ThreadLocal<SimpleDateFormat>() {
                      @Override
                      protected SimpleDateFormat initialValue() {
                          return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                      }
                  };
          private static AtomicInteger count = new AtomicInteger(0);
      
          @Override
          public void run() {
              if (0 == count.get()) {
                  System.out.println("Long begin :" + format.get().format(new Date()));
                  SleepTools.second(Long);
                  System.out.println("Long end :" + format.get().format(new Date()));
                  count.incrementAndGet();
              } else if (1 == count.get()) {
                  System.out.println("Short begin :" + format.get().format(new Date()));
                  SleepTools.second(Short);
                  System.out.println("Short end :" + format.get().format(new Date()));
                  count.incrementAndGet();
              } else {
                  System.out.println("Normal begin :" + format.get().format(new Date()));
                  SleepTools.second(Normal);
                  System.out.println("Normal end :" + format.get().format(new Date()));
                  count.incrementAndGet();
              }
          }
      
      
          public static void main(String[] args) {
              ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(1);
      
              pool.scheduleAtFixedRate(new ScheduledThreadWorkTime(),
                      0, 6000, TimeUnit.MILLISECONDS);
      
          }
      }
      

      输出如下

      Long begin :2020-04-22 15:04:35
      Long end :2020-04-22 15:04:43
      Short begin :2020-04-22 15:04:43
      Short end :2020-04-22 15:04:45
      Normal begin :2020-04-22 15:04:47
      Normal end :2020-04-22 15:04:52
      Normal begin :2020-04-22 15:04:53
      Normal end :2020-04-22 15:04:58
      Normal begin :2020-04-22 15:04:59
      

Executor框架

image-20200422150707189

CompletionService

例如我们要获取到一个线程池里面每个线程的返回值,我们使用Executor我们就需要使用一个阻塞队列来存储返回值,这样的话,我们的元素时先进先出,就意味着第一个进去的任务没有执行完成,不管之后的任务有没有执行完成都无法拿到数据,自由第一个任务完成后才能取拿第二个任务的返回值,这样就造成了浪费。

CompletionService是对Exector的一种补充,就上面的逻辑,Completion继承了FutureTask,并且重写了done方法,所以compeletion在往队列插入的时候都是完成在前面

两种方式对比如下

输出如下

工作线程

    private String name;


    public WorkTask(String name) {
        this.name = name;
    }


    @Override
    public Integer call() throws Exception {

        int sleepTime = new Random().nextInt(1000);

        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {

        }
        return sleepTime;
    }
}
public class CompletionCase {
    private final int POOL_SIZE = Runtime.getRuntime().availableProcessors();
    private final int TOTAL_TASK = Runtime.getRuntime().availableProcessors();


    //自己写集合来获取线程池中的结果
    public void testQueue() throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
        AtomicInteger count = new AtomicInteger(0);
        BlockingQueue<Future<Integer>> queue = new LinkedBlockingQueue<>();

        for (int i = 0; i < TOTAL_TASK; i++) {
            Future<Integer> future = pool.submit(new WorkTask("MyQueue" + i));
            queue.add(future);//i=0 先进队列,i=1的任务跟着进
        }

        for (int i = 0; i < TOTAL_TASK; i++) {
            Integer sleepTime = queue.take().get();
            System.out.println("sleep " + sleepTime + " ms");
            count.getAndAdd(sleepTime);
        }

        pool.shutdown();

        System.out.println("-----------task sleep time " + count.get() + " and spend time " + (System.currentTimeMillis() - start) + " ms");
    }

    //使用CompletionService
    public void useCompletion() throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        AtomicInteger count = new AtomicInteger(0);

        ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
        ExecutorCompletionService<Integer> service = new ExecutorCompletionService<>(pool);

        for (int i = 0; i < TOTAL_TASK; i++) {
            service.submit(new WorkTask("ComQueue" + i));
        }

        for (int i = 0; i < TOTAL_TASK; i++) {
            Integer sleepTime = service.take().get();
            System.out.println("sleep " + sleepTime + " ms");
            count.getAndAdd(sleepTime);
        }

        // 关闭线程池
        pool.shutdown();
        System.out.println("-------------tasks sleep time " + count.get()
                + "ms,and spend time "
                + (System.currentTimeMillis() - start) + " ms");
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletionCase aCase = new CompletionCase();
        aCase.testQueue();
        aCase.useompletion();
    }
}
sleep 924 ms
sleep 293 ms
sleep 806 ms
sleep 660 ms
sleep 91 ms
sleep 372 ms
sleep 872 ms
sleep 859 ms
sleep 634 ms
sleep 562 ms
sleep 818 ms
sleep 752 ms
-----------task sleep time 7643 and spend time 932 ms
sleep 134 ms
sleep 198 ms
sleep 233 ms
sleep 360 ms
sleep 369 ms
sleep 547 ms
sleep 665 ms
sleep 693 ms
sleep 730 ms
sleep 736 ms
sleep 784 ms
sleep 807 ms
-------------tasks sleep time 6256ms,and spend time 815 ms