线程池工作原理

         Java的线程池是当前应用最普遍的一种并发框架。在并发编程中,合理的使用线程池能够带来一系列的好处。

        1、降低资源消耗。线程池通过重复利用已创建的线程,从而降低线程创建和销毁的开销;

        2、提高响应速度。当任务到达时,任务可以不用等待创建线程,利用现成的线程资源就能立即执行。例如,完成一项任务需要时间分为:T1线程创建的时间,T2在线程中执行的时间,T3线程销毁的时间。线程池通过把T1、T3安排在空闲的时间执行。在有任务提交过来时,只消耗了T2执行任务的时间,从而提高了相应的时间。对于执行时间非常短的线程,T1+T3远大于T2,性能和响应速度提高的更加明显。

        3、提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

线程池中类关系 

        Executor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。

        ExecutorService接口继承了Executor,在其上做了一些shutdown()、submit()的扩展,是真正的线程池接口。

        AbstractExecutorService抽象类实现了ExecutorService接口中的大部分方法。

        ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。

        ScheduledExecutorService接口继承了ExecutorService接口,提供了带"周期执行"的功能。

        ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。

创建线程池

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

corePoolSize:线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize。如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

maximumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。

keepAliveTime:线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只对非核心线程起作用,即在线程数大于corePoolSize时才有用。核心线程创建了会一直存活。可以通过allowCoreThreadTimeOut()方法设置对核心线程也起作用。

TimeUnit:keepAliveTime的时间单位,有ns、ms、s、m、h等。

workQueue:是阻塞队列(BlockingQueue)。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。

threadFactory:线程创建工厂,通过自定义的工厂可以给每个新建的线程设置一个具有识别度的线程名,当然还可以更加自由的对线程做更多的设置,比如设置所有的线程为守护线程等。线程池默认的threadFactory,将线程按照“pool-数字-thread-数字”规则命名。

RejectedExecutionHandler:线程池拒绝策略。当阻塞队列满了,且没有空闲的工作线程,此时继续提交任务,则执行拒绝策略。用户可以自定义拒绝策略,进行记录日志或者对任务进行持久化等。线程池本身提供了4种策略:

        AbortPolicy:直接抛出异常,默认策略;

        CallerRunsPolicy:用调用者所在的线程来执行任务;

        DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

        DiscardPolicy:直接丢弃任务;

线程池工作机制

        上文介绍线程池各参数的时候,已经提到了线程池的工作流程。这里单独提出来总结。当用户用户提交任务。

        1、当前运行任务的线程少于corePoolSize,则创建新线程来执行任务;

        2、当前运行任务的线程数大于等于corePoolSize,则将任务加入BlockingQueue;

        3、阻塞队列已满,新的任务无法加入,则再创建新的线程来处理;

        4、运行任务的线程数超过maximumPoolSize,则调用RejectedExecutionHandler.rejectedExecution() 方法,执行拒绝策略。

同步提交与异步提交

        提交任务到线程池执行,有同步提交和异步提交两种。同步提交会等待线程池返回,而异步提交则不需要。

        execute()方法用于异步提交,提交不需要返回值的任务。该方法无法判断任务是否被线程池执行成功。通常任务都采用异步提交。ThreadPoolExecutor只实现了异步提交。ScheduledThreadPoolExecutor实现了同步提交和异步提交。

        submit()方法用于同步提交,提交需要有返回值的任务。线程池会返回一个Future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候任务有可能没有执行完。

public interface Future<V> {
    // 取消执行,即调用中断线程
    boolean cancel(boolean mayInterruptIfRunning);
    // 判断线程是否已经被取消
    boolean isCancelled();
    // 判断线程是否执行完
    boolean isDone();
    // 获取线程执行返回值
    V get() throws InterruptedException, ExecutionException;
    // 获取线程执行返回值,带有超时时间
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

关闭线程池

        线程池提供shutdownshutdownNow两种方法,进行线程池的关闭操作。他们的实现原理都是遍历所有在执行的线程,调用它们的interrupt()方法,进行线程中断。因此,我们在编写提交任务时,要设计良好的响应中断的任务。否则,无法响应中断的任务可能无法进行终止。

        这两个方法的区别是,shutdownNow将线程池的状态设置成STOP,然后中断所有的正在执行任务(包括中途暂停的)线程。shutdown是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有执行任务线程。通常通过调用shutdown方法来关闭线程池,而任务如果不用执行完,则可以调用shutdownNow方法。

        当调用了shutdown、shutdownNow方法,isShutdown()方法就会返回true。当方法执行完,线程池关闭成功,此时调用isTerminated()返回true。

合理配置线程池

        一般根据任务的特性,进行线程池的配置。常用的是根据“任务性质”维度,将任务分为CPU密集型任务、IO密集型任务和混合型任务。其他维度还有:任务优先级、任务执行时间等。

任务性质维度

        CPU密集型任务,应配置尽可能小的线程,如配置cpu核心数+1个线程的线程池;

        (“cpu核心数+1”的由来:内存是有限的,操作系统会把一步磁盘虚拟为内存来用,发生加载缺失页时,线程会等待,让出CPU。因此,通过“+1”将该cpu核心充分利用起来。)

        IO密集型任务,线程在执行任务频率低,应配置尽可能多的线程,如2*cpu核心数。

        混合型的任务,将其拆分成一个CPU密集型任务和一个IO密集型任务,分别建立线程池。如果两个任务执行时间相差很大,则根据任务的侧重点,按照CPU密集或者IO密集型任务处理。

        通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

任务优先级维度:   

        不同的任务使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行。

任务执行时间维度

        执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值