1. 池化思想

线程池、字符串常量池、数据库连接池等
线程池作用:提高资源的利用率

2. 不用线程池

  1. 手动创建线程对象
  2. 执行任务
  3. 执行完毕,释放线程对象

3. 线程池优点

  1. 提高线程利用率
  2. 提高程序响应速度
  3. 便于统一管理线程对象
  4. 可以控制最大并发数

4. 线程池重要的参数

线程池的实现类是ThreadPoolExecutor,它是java.util.concurrent下的类
它有四个构造方法


	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    

	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

	
	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }


	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;
    }

我们观察可知,线程池有5个必需的参数,和两个可选参数。

4.1 五个必需参数

4.1.1 corePoolSize(int)

核心线程数。默认情况下不会超时回收的线程。

4.1.2 maximumPoolSize(int)

线程池所能容纳的最大线程数。核心线程都活跃后,如果再有任务进入,则进入任务等待队列。如果等待队列也满了,就会在线程池创建新的线程,处理新进来的请求。如果线程数已经达到线程池所能容纳的最大线程数,且等待队列也满了,新进来的任务就会走拒绝策略。

4.1.3 keepAliveTime(long)

线程闲置超时时长。如果超过该时长,非核心线程会被回收。

4.1.4 unit(TimeUnit)

指定keepAliveTime参数的时间单位,例如毫秒、秒、分。

4.1.5 workQueue(BlockingQueue<Runnable>)

任务等待队列。通过线程池的execute()方法提交的Runnable对象将存储在该参数中。这个任务队列采用阻塞队列实现。
BlockingQueue<Runnable>有以下7种实现类:

ArrayBlockingQueue

是 BlockingQueue 接口的有界队列实现类,底层采用数组来实现。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。
使用ArrayBlockingQueue时,必须指定其容量大小。

SynchronousQueue

这里的同步说的并不是多线程的并发问题,而是当一个线程往队列中写入一个元素时,写入操作不会立即返回,需要等待另一个线程来将这个元素拿走。也就是说,读线程和写线程需要同步,一个读线程匹配一个写线程。

LinkedBlockingDeque

一个双向队列,任何一端都可以进行元素的出入。底层基于单向链表实现的阻塞队列,可无界可有界。

LinkedBlockingQueue

一个单向队列,有FIFO特性,通过两个ReentrantLock和两个Condition来实现。底层基于单向链表实现的阻塞队列,可无界可有界。

DelayQueue

支持延时获取元素的无界阻塞队列,内部用 PriorityQueue 实现。

LinkedTransferQueue

由单向链表组成的无界阻塞队列,其内部节点分为数据节点与请求节点。基于CAS无锁算法实现。

PriorityBlockingQueue

带排序的BlockingQueue实现,通过ReentrantLock控制并发,为无界队列。不可以插入null值,且插入队列的对象必须是可比较大小的。

4.2 两个可选参数

4.2.1 threadFactory(ThreadFactory)

线程工厂。用于指定为线程池创建新线程的方式。

4.2.2 handler(RejectedExecutionHandler)

拒绝策略。当活跃线程已达到线程池最大容量,且阻塞队列也满了时,采取的一种策略 。

5. 线程池如何执行?

ThreadPoolExecutor的继承结构
类 ThreadPoolExecutor 继承 抽象类 AbstractExecutorService
抽象类 AbstractExecutorService 实现 接口 ExecutorService
接口 ExecutorService 继承 接口 Executor


继承结构图

接口 Executor 有一个方法

void execute(Runnable command);

经过ThreadPoolExecutor的重写后,它的源码是这样的

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        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);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

我们执行线程池用的就是这个方法。

public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));

        for (int i = 0; i < 8; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 在办理业务");
            });
        }
        executor.shutdown();
    }

上面创建的线程池,最大线程容量为5个,有3个核心线程,非核心线程会在闲置超过1s后进行回收,等待队列选择的是ArrayBlockingQueue,长度为3。线程工厂以及拒绝策略都选用的默认。

所以这个线程池最多可以接受8个任务的访问,例如上面我们模拟了8次请求,得到了正确的处理

pool-1-thread-1 在办理业务
pool-1-thread-2 在办理业务
pool-1-thread-5 在办理业务
pool-1-thread-4 在办理业务
pool-1-thread-3 在办理业务
pool-1-thread-2 在办理业务
pool-1-thread-1 在办理业务

如果我们把for循环里的8改成9呢?前八次执行都是正常的,最后一次报错了。
java.util.concurrent.RejectedExecutionException
说明线程池已经满载,采用了拒绝策略。

6. 四种线程池

这是Executors为我们封装好的4种常见功能线程池。

6.1 newCachedThreadPool 可缓存线程池

可无限扩大的线程池,适合处理执行时间比较短的任务。
如果线程池长度超过需要处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
缺点:可能创建非常多的线程,造成资源浪费,甚至OOM。

6.2 newFixedThreadPool 固定数目线程池

定长线程池,可以控制线程最大并发数,超出的任务会在队列中等待。
缺点:如果问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

6.3 ScheduledThreadPool 定时线程池

定长线程池,支持定时即周期性任务执行。
缺点:可能创建非常多的线程,造成资源浪费,甚至OOM。

6.4 newSingleThreadExecutor 单线程化线程池

单线程线程池,保证所有任务先进先出执行。
缺点:可能创建非常多的线程,造成资源浪费,甚至OOM。

6.5 线程池规范性

阿里巴巴Java开发手册告诉我们:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 各个方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
2)newCachedThreadPool 和 newScheduledThreadPool:
主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

GitHub 加速计划 / th / ThreadPool
7
0
下载
A simple C++11 Thread Pool implementation
最近提交(Master分支:1 个月前 )
9a42ec13 - 10 年前
fcc91415 - 10 年前
Logo

新一代开源开发者平台 GitCode,通过集成代码托管服务、代码仓库以及可信赖的开源组件库,让开发者可以在云端进行代码托管和开发。旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐