Thread Pool(线程池)技术

Thread Pool(线程池)技术

技术背景介绍

在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内
存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,
以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可
能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用
已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术
产生的原因。比如大家所熟悉的数据库连接池正是遵循这一思想而产生的

备注:与线程池会一同出现的概念是数据库连接池,”池化资源”。

在频繁使用线程的场景下,使用线程池是很有必要的可以大大节约服务器资源,
尤其是线程处理的是小任务很快就结束的任务。如果不是频繁使用线程
场景下,并且线程处理的是费时的大任务那么其实不使用线程池也没关系,直接
使用new Thread()来创建线程也没多大影响。

备注:并发数大并且每个任务都是短时间能够完成的,那么这种场景就比较适合使用线程池技术。

Java中线程池实现方案

ThreadPoolExecutor

在jdk1.5之后在java.util.concurrent并发包下ThreadPoolExecutor类可以帮我们实现线程池功能。

ThreadPoolExecutor Code 构造方法(省略了其它三个)

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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
备注:其余几个构造方法和这个差不多,只是入参不同罢了。

入参详细解释

corePoolSize

核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建
了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创
建线程去执行任务,除非调用了prestartAllCoreThreads()或者
prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的
意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情
况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一
个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的
任务放到缓存队列当中

maximumPoolSize

线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

keepAliveTime

表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中
的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的
线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如
果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数
不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方
法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起
作用,直到线程池中的线程数为0;

workQueue

工作队列通常有以下几种选择
ArrayBlockingQueue//数组阻塞队列
LinkedBlockingQueue//链表阻塞队列
SynchronousQueue//同步阻塞队列

threadFactory

线程工厂,主要用来创建线程

handler

表示当拒绝处理任务时的策略,有以下四种取值
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

ThreadPoolExecutor的层次结构

    public class ThreadPoolExecutor extends AbstractExecutorService {...}

    public abstract class AbstractExecutorService implements ExecutorService {...}

    public interface ExecutorService extends Executor {...}

    public interface Executor {

        void execute(Runnable command);//执行方法(最核心)
    }

    //备注:
    //1、Executor执行器接口定义了一个执行任务方法
    //2、ExecutorService执行器管理者接口定义了一些额外的管理方法并且还继承了Executor接口
    //3、AbstractExecutorService抽象类实现了ExecutorService部分方法
    //4、ThreadPoolExecutor继承AbstractExecutorService抽象类并个性化了些方法,供用户使用。

使用Executors来创建线程池

备注:虽然使用ThreadPoolExecutor的构造方法获取线程池对象已经很方便了,但是它那么多入参需要配置让我们很蒙逼所以jdk提供了Executors类,使用其静态方法可以更简单实用线程池技术。

newFixedThreadPool(int nThreads);
newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
ExecutorService newSingleThreadExecutor();
newSingleThreadExecutor(ThreadFactory threadFactory);
newCachedThreadPool();
newCachedThreadPool(ThreadFactory threadFactory);
newSingleThreadScheduledExecutor();
newSingleThreadScheduledExecutor(ThreadFactory threadFactory);
newScheduledThreadPool(int corePoolSize);
newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);

//简直不要太容易

线程池实现原理

线程池状态

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;//
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
先不管移位运算就看英文含义

当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够
接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受
新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列
已经清空或执行结束后,线程池被设置为TERMINATED状态。

线程池中的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

  如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

    /**
     * Starts a core thread, causing it to idly wait for work. 
     * 初始化一个核心线程
     */
    public boolean prestartCoreThread() {
        return workerCountOf(ctl.get()) < corePoolSize &&
            addWorker(null, true);
    }

    /**
     * Starts all core threads, causing them to idly wait for work. 
     * 初始化所有核心线程
     */
    public int prestartAllCoreThreads() {
        int n = 0;
        while (addWorker(null, true))
            ++n;
        return n;
    }

任务缓存队列及排队策略

workQueue的类型为BlockingQueue,通常可以取下面三种类型:

1、ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2、LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队
列大小,则默认为Integer.MAX_VALUE;
3、synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直
接新建一个线程来执行新来的任务。

任务拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

1、shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行
完后才终止,但再也不会接受新的任务
2、shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任
务缓存队列,返回尚未执行的任务

demo 简单使用

public class MainApp {
public static void main(String[] args) {
        // ExecutorService executorService = Executors.newFixedThreadPool(3);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 20, 15, TimeUnit.SECONDS,
                new ArrayBlockingQueue(5));

        for (int i = 1; i <= 20; i++) {
            threadPoolExecutor.execute(new TaskThread(i));
            System.out.println("已完成的任务数:" + threadPoolExecutor.getCompletedTaskCount());
            System.out.println("getLargestPoolSize" + threadPoolExecutor.getLargestPoolSize());
            System.out.println("getPoolSize" + threadPoolExecutor.getPoolSize());
            System.out.println("--------------------------------");
        }
        threadPoolExecutor.shutdown();
    }
}

任务类 Code

public class TaskThread implements Runnable {
    private int i;
    public TaskThread(int i) {
        this.i = i;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread" + i + "任务执行完毕...");

    }
}

备注:配置线程池的线程数是比较麻烦的一件事,通常要考虑很多因素比如任务类型是IO密集型的还是CPU密集型的?以及实际生产环境下的各种限制条件。比较好的办法就试验和测试得出结论。

对于计算密集型的任务,在拥有N个处理器的系统上,当线程池的大小为N+1时,
通常能实现最优的效率。(即使当计算密集型的线程偶尔由于缺失故障或者其他原
因而暂停时,这个额外的线程也能确保CPU的时钟周期不会被浪费。)

如果是IO密集型的任务通常是线程数越多越好(越多当然也就越消耗内存资源),通常可以设置为2N+1.

参考

http://www.infoq.com/cn/articles/java-threadPool/
https://www.ibm.com/developerworks/cn/java/l-threadPool/
http://www.cnblogs.com/dolphin0520/p/3932921.html
https://en.wikipedia.org/wiki/Thread_pool
https://www.ibm.com/developerworks/cn/java/j-jtp0730/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值