4. 线程池使用

线程池: 如果线程的数量很多,并且每个线程都是执行一个时间很短的任务就结束,这样频繁创建线程会大大增加系统的开销,因为创建和消毁线程都需要资源和时间的

线程池有4种

1) newCachedThreadPool

新建一个可缓存线程,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

2) newFixedThreadPool

创建一个固定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

3) newScheduleThreadTool

创建一个定长线程池,支持定时及周期性任务执行

4) newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO)优先级执行

简单实例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TreadPoolDemo {
    public static void main(String[] args) {                                            //执行速度
        ExecutorService executorService1 = Executors.newCachedThreadPool();             //快
        ExecutorService executorService2 = Executors.newFixedThreadPool(10);  //中等
        ExecutorService executorService3 = Executors.newSingleThreadExecutor();         //慢

        for (int i = 0; i < 100; i++) {
            executorService1.execute(new MyTask(i));
//            executorService2.execute(new MyTask(i));
//            executorService3.execute(new MyTask(i));

        }
    }
}
class MyTask implements Runnable{
    int i = 0;
    public MyTask(int i) {
        this.i = i;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"一执行第:"+i+"任务");
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

分析:

  1. newCachedThreadPool启动源码:

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

    核心线程数: 0
    最大线程数为: Integer.MAX_VALUE
    线程空闲回收时间为: 60秒
    工作队列为: SynchronousQueue (这个队列只接受一个任务,下面会讲解到)

    newCachedThreadPool个人总结 :
    所以该线程池执行的逻辑是,有多少个任务我就开启多少个线程去运行该任务(缺点:造成性能的浪费,容易导致服务器性能崩溃.优点是:执行速度快)

  2. newFixedThreadPool启动源码

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

    核心线程数: 10 (传入的线程数代码上是10)
    最大线程数为: 10 (传入的线程数代码上是10)
    线程空闲回收时间为: 0
    工作队列为: LinkedBlockingQueue (这个队列能存储多个任务,下面会讲解到)

    newFixedThreadPool个人总结 :
    线程可复用,执行速度合理,不存在非核心线程(优先: 线程复用性强 缺点:初始化后不能修改线程数目,遇到高并发的情况也只能使用初始设定的线程去执行任务,导致用户体验较差)

  3. newSingleThreadExecutor启动源码

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

    核心线程数: 1
    最大线程数为: 1
    线程空闲回收时间为: 0
    工作队列为: LinkedBlockingQueue (这个队列能存储多个任务,下面会讲解到)

    newSingleThreadExecutor个人总结 :
    只有一个线程处理任务队列(缺点: 用户体检极差,处理能力极慢)

以上就是直接new一个线程池出来运行,但是阿里开发手册禁止使用该方法来启动一个线程池.而推荐使用ThreadPoolExecutor来创建

原因:

  1. 比如执行newFixedThreadPool时会创建一个Integer.MAX_VALUE的线程队列来存储线程任务,所有当线程任务过多是(占用资源过多),会导致oom的问题
  2. 不能很好的配置核心线程数和最大线程数,导致资源使用不合理,定义参数不合理

ThreadPoolExecutor讲解

先看ThreadPoolExecutor源码

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue 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;
}

构造器提供了的参数我们来分析一下

  • int corePoolSize

    线程池中核心线程数的最大值

  • int maximumPoolSize

    线程池中能拥有最多线程数

  • long keepAliveTime

    表示空闲线程的存活时间(线程回收时间)

  • TimeUnit unit

    表示keepAliveTime的单位

  • BlockingQueue workQueue

    用于缓存任务阻塞队列

    workQueue任务队列:
    对于不用的场景我们可能会采取不同的排队策略,排队的策略需要实现一个BlockQueue接口的任务等待队列一下任务队列分为两类

    1. 有限队列
      1. SynchronousQueue: (一个不存元素的阻塞队列)
        每个插入操作必须等到另一个线程调用移除操作否则插入操作一值处于阻塞状态,吞吐量通常高于LinkedBlockQueue
        2.ArrayBlockQueue: (有界阻塞队列)
        一个由数组支持的有界队列,此队列按FIFO(先进先出)原则对元素进行排序,队列获取从头部开始获得元素,从尾部出入到队列.固定大小的数据,一但创建了缓存区,就不能再增加容量了. 向已满的队列里放入元素会导致操作受阻塞,向空的队列中提取元素也会导致阻塞
    2. 无限队列
      1. LinkedBlockingQueue: (链表结构的阻塞队列)原理和ArrayBlockQueue队列一样,但是你可以指定容量,也可以不指定容量,不指定容量就是Integer.MAX_VALUE
      2. PriorityBlockIngQueue(一个具有优先级的无限阻塞队列)存放在PriorityBlockingQueue中的元素必须实现Comparable接口,这样才能通过实现compareTo()方法进行排序.优先级最高的元素将始终排序到队列的头部,不会保证优先级一样的元素排序.
      3. LinkedBlockDeque和LinkedBlockingQueue一样是基于链表的队列,但是跟LinkedBlockingQueue不同的是,他两头都可以插入和取出元素
      4. LinkedTransferQueue 也是一个无限队列,它除了具有一般队列的操作特性外(先进先出),还具有一个阻塞特性:LinkedTransferQueue可以由一对生产者/消费者线程进行操作,当消费者将一个新的元素插入队列后,消费者线程将会一直等待,直到某一个消费者线程将这个元素取走,反之亦然。
  • ThreadFactory threadFactory

    指定创建线程的工厂

  • RejectedExecutionHandler handler

    表示当workQueue队列满了,且池中的线程数到达maximumPoolSize时,线程池采用的拒绝策略

策略 备注
ThreadPoolExecutor.AbortPolicy() 抛出RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy() 由向线程池提交任务的线程来执行该任务
ThreadPoolExecutor.DiscardPolicy() 抛弃当前的任务
ThreadPoolExecutor.DiscardOldestPolicy() 抛弃最旧的任务(最先提交而没有得到执行的任务)

阿里推荐我们使用ThreadPoolExecutor来创建线程池,原因是这样创建线程比较灵活,可以根据自己的业务去定制线程池

ThreadPoolExecutor源码这里就先不讲解放到下篇文章讲解,这里说说ThreadPoolExecutor的执行过程,如何触发maximumPoolSize的执行线程启动
上例子:

import java.util.concurrent.*;

public class TreadPoolDemo {
    public static void main(String[] args) { 
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        // 3个核心线程
        3, 
        // 总共12个最多线程
        12,
        // 10秒空闲时间就回收
        10, 
        TimeUnit.SECONDS,
        //工作队列大小
        new ArrayBlockingQueue<>(50),
        //策略为: 超出后丢弃
        new ThreadPoolExecutor.DiscardPolicy());
int i = 0;
        for (;;) {
            i ++;
            threadPoolExecutor.execute(new MyTask(i));

        }
    }
}
class MyTask implements Runnable{
    int i = 0;
    public MyTask(int i) {
        this.i = i;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"---执行第:"+i+"任务");
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

以上的执行结果我拷贝一定出来分析:

pool-4-thread-1一执行第:1任务
pool-4-thread-2一执行第:2任务
pool-4-thread-3一执行第:3任务
pool-4-thread-4一执行第:54任务
pool-4-thread-5一执行第:55任务
pool-4-thread-6一执行第:56任务
pool-4-thread-7一执行第:57任务
pool-4-thread-8一执行第:58任务
pool-4-thread-9一执行第:59任务
pool-4-thread-10一执行第:60任务
pool-4-thread-11一执行第:61任务
pool-4-thread-12一执行第:62任务
pool-4-thread-3一执行第:4任务
pool-4-thread-2一执行第:6任务
pool-4-thread-1一执行第:7任务
pool-4-thread-4一执行第:5任务
pool-4-thread-8一执行第:8任务
pool-4-thread-7一执行第:9任务
pool-4-thread-6一执行第:10任务
pool-4-thread-5一执行第:11任务
pool-4-thread-12一执行第:12任务
pool-4-thread-11一执行第:13任务
pool-4-thread-10一执行第:14任务
......
pool-4-thread-2一执行第:29381082任务
pool-4-thread-4一执行第:29381081任务
pool-4-thread-8一执行第:29385303任务
...

看到执行结果的朋友们都惊呆了,为什么这执行顺序1,2,3执行的一下子跳到54个任务,然后处理完62的任务又执行4以下的任务,接下来的顺序就正常了

原因: 定义线程池是核心为3个线程,所以执行了1-3任务,没问题!到后面线程pool-4-thread-(4-12)都是 maximumPoolSize - 核心线程 = 要创建的临时线程数.这里是 重点 ,这些临时线程是当你工作队列满了的情况才创建出来的, 而工作队列里面的任务是不先处理,而且让工作队列存放不下的任务,直接交给临时线程处理,所以临时线程直接执行了54-62任务,然后处理完之后在去队列里面从头获取任务执行,所以执行完62任务后后面的执行顺序就正常了.运行到了下面,直接任务数到了29381082时,这里我是启动了任务执行处理不来是或工作队列满了时,任务丢弃了,所以存到工作队列任务的数字就是29381082,然后就通过线程去获取任务指定任务

你可能感兴趣的:(4. 线程池使用)