JAVA并发编程——线程池

概述

前面所说的都是通过创建线程的方法使程序并行运行,一般的做法是,创建一个Runnable对象,然后封装为Thread对象,通过start方法启动线程,并在线程运行过程中通过sleep、interrupt等方法来控制线程的运行。可见,在之前的程序书写中,线程的创建、运行、休眠和终止都是手动完成的。 如果创建线程较少,这种手动操作并不繁琐,但如果创建线程过多,这种手动操作就显得有些繁琐。

在一个最多支持四个线程同时运行的多核处理器上,如果创建的线程数少,则多核处理器的处理能力没有被充分利用,如果创建的线程数过多,则机器的资源不够,多余的线程只能以时间片轮转的方式运行,或者等待。可见,线程创建的过多或过少都不好,一般情况下,是让线程数等于多核处理器可以同时处理的线程数。

创建一个新线程的代价很高,尤其当创建多个生命期较短的线程时,程序的执行时间有可能浪费到线程的创建和销毁上,这对于提高程序的性能没有任何帮助。有时,创建大量的线程反而会降低程序的性能,,严重的会使虛拟机崩溃。

线程池包含了若干个准备运行的空闲线程,线程在程序运行的开始创建,可以把创建的Runnable对象交给线程池中的线程运行,运行完成后,如果没有其他任务,线程转人休眠状态,等有任务的时候再唤醒,直到所有任务都执行结束再关闭线程池,从而减少了重复创建和销毁线程的开销。

线程池机制分离了任务的创建和执行。使用线程池执行器,仅仅需要实现Runnable对象,并将该对象交给执行器。执行器会使用线程池中的线程执行,避免额外创建线程的开销。线程池执行器起到了维护和管理线程的作用,从而把程序员从繁重的线程管理任务中解放出来。

从JDK 1.5开始,Java并发库中引人了Executor 框架。该框架包括了接口Executor及其子接口ExecutorService,以及实现了这两个接口的类ThreadPoolExecutor

描述

为什么要用线程池

1、 降低我们的资源消耗
2、 提高我们的响应速度

  • T1:线程创建时间;
  • T2:任务运行的时间;
  • T3:销毁线程

3、 提高了线程的可管理性

接口Executor

public interface Executor {
    void execute(Runnable command);
}

接口Executor的对象可以接收提交到线程池的Runnable任务,该接口实现了任务提交与任务的执行分离。

该接口定义了一个execute的方法,用于异步的执行给定的Runnable对象。

接口ExecutorService

接口ExecutorService从父类接口Executor继承,定义的形式如下:

public interface ExecutorService extends Executor {}

接口ExecutorService提供了关闭线程池的方法shutdown,关闭后的线程池将不再接收新的任务。

类ThreadPoolExecutor

类ThreadPoolExecutor可以用来构建一个线程池,定义的形式如下:

public class ThreadPoolExecutor extends AbstractExecutorService 

它有四个构造方法:

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

下面解释一下构造器中各个参数的含义:
corePoolSize:核心线程数。核心池的大小。在创建了线程池后,默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

maximumPoolSize:线程池允许的最大线程数,它表示在线程池中最多能创建多少个线程。

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

unit:参数keepAliveTime的时间单位。

workQueue:一个阻塞队列,用来存储等待执行的任务。一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。

threadFactory:线程工厂,主要用来创建线程。缺省的线程的命名规则: pool-数字+thead-数字。

RejectedExecutionHandler handler :饱和策略。表示当拒绝处理任务时的策略。

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

线程池的工作机制

线程池的工作机制
在这里插入图片描述

1、如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2、如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
3、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

线程池的使用范例

public class UseThreadPool {
    static class Worker implements Runnable
    {
        private String taskName;
        private Random r = new Random();

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

        public String getName() {
            return taskName;
        }

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

    public static void main(String[] args)
    {
        ExecutorService threadPool = new ThreadPoolExecutor(2, 4, 3,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        //ExecutorService cachePool = Executors.newCachedThreadPool();
        //ExecutorService fixPool = Executors.newFixedThreadPool(2);
        //ExecutorService singlePool = Executors.newSingleThreadExecutor();
        //ExecutorService forkjoinPool = Executors.newWorkStealingPool();
        for (int i = 0; i <= 6; i++)
        {
            Worker worker = new Worker("worker " + i);
            System.out.println("A new task has been added : " + worker.getName());
            threadPool.execute(worker);
        }
        threadPool.shutdown();
    }
}

A new task has been added : worker 0
A new task has been added : worker 1
A new task has been added : worker 2
A new task has been added : worker 3
pool-1-thread-1 process the task : worker 0
pool-1-thread-2 process the task : worker 1
A new task has been added : worker 4
A new task has been added : worker 5
A new task has been added : worker 6
pool-1-thread-3 process the task : worker 5
pool-1-thread-4 process the task : worker 6
pool-1-thread-2 process the task : worker 2
pool-1-thread-3 process the task : worker 3
pool-1-thread-1 process the task : worker 4

工厂类Executors

在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池。如下所示:

newFixedThreadPool

创建一个固定大小的线程池,空闲线程会一直保留。适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。

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

newSingleThreadExecutor

创建一个线程的线程池,显然该线程池将顺序执行每一次提交的任务。适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。

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

newCachedThreadPool

创建一个会根据需要创建新线程的大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。

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

newWorkStealingPool

利用所有运行的处理器数目来创建一个工作窃取的线程池,使用fork/join实现。

public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。

newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;

newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。

相关博客

Java并发编程:线程池的使用

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值