JAVA并发编程——线程池

概述

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

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

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

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

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

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

JAVA并发编程——线程池_第1张图片

为什么要用线程池

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 workQueue)
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue 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:由调用线程处理该任务。

线程池的工作机制

JAVA并发编程——线程池_第2张图片
JAVA并发编程——线程池_第3张图片

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

newSingleThreadExecutor

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

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

newCachedThreadPool

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

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

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并发编程:线程池的使用

你可能感兴趣的:(Java,线程池)