• Java ExecutorService四种线程池及ThreadPoolExecutor机制


    一、为什么使用线程池

    使用new Thread执行多个线程有如下一些问题:

    每次new Thread新建对象性能差。
    线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
    缺乏更多功能,如定时执行、定期执行、线程中断。
    相比new Thread,Java提供的四种线程池的好处在于:

    重用存在的线程,减少对象创建、消亡的开销,性能佳。
    可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
    提供定时执行、定期执行、单线程、并发数控制等功能。

    二、怎么使用线程池

    java中提供的四种线程池都是Executors提供的,共计四种。

    1, newCachedThreadPool

    创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
    final int index = i;
    try {
    Thread.sleep(index * 1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    cachedThreadPool.execute(new Runnable() {

    @Override
    public void run() {
    System.out.println(index);
    }
    });
    }

    线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

    2, newFixedThreadPool

    创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
    final int index = i;
    fixedThreadPool.execute(new Runnable() {


    @Override
    public void run() {
    try {
    System.out.println(index);
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    });
    }

    因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。 定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。可参考PreloadDataCache。

    3, newScheduledThreadPool

    创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    scheduledThreadPool.schedule(new Runnable() {

    @Override
    public void run() {
    System.out.println("delay 3 seconds");
    }
    }, 3, TimeUnit.SECONDS);

    表示延迟3秒执行。

    定期执行示例代码如下:

    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
    System.out.println("delay 1 seconds, and excute every 3 seconds");
    }
    }, 1, 3, TimeUnit.SECONDS);

    表示延迟1秒后每3秒执行一次。

    4, newSingleThreadExecutor

    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
    final int index = i;
    singleThreadExecutor.execute(new Runnable() {

    @Override
    public void run() {
    try {
    System.out.println(index);
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    });
    }

    结果依次输出,相当于顺序执行各个任务。 现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作。

    三、分析四种线程池

    先看下四种线程池的构建代码:

    1, newCachedThreadPool

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

    2, newFixedThreadPool

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

    3, newScheduledThreadPool

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
            return new ScheduledThreadPoolExecutor(corePoolSize);
        }
    
    public ScheduledThreadPoolExecutor(int corePoolSize) {
            super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
                  new DelayedWorkQueue());
        }
    
    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue) {
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 Executors.defaultThreadFactory(), defaultHandler);
        }

    4, newSingleThreadExecutor

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

    从上面可以看出这四种构造方法最终都是返回了ThreadPoolExecutor对象,下面重点分析下它,看下它的构造方法:

    public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
    int maximumPoolSize,//最大线程池大小
    long keepAliveTime,//线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)成为核心线程的有效时间
    TimeUnit unit,//keepAliveTime的时间单位
    BlockingQueue<Runnable> workQueue,//阻塞任务队列
    ThreadFactory threadFactory,//线程工厂
    RejectedExecutionHandler handler) {//当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理
    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,maximumPoolSize,workQueue之间关系。

    当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
    当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行。
    当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务。
    当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理。
    当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程。
    当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭。
    那么学会使用ThreadPoolExecutor的参数后,我们就可以不用局限于最上面那四种线程池,可以按照需要来构建自己的线程池。

    另外,通过ThreadFactory可以实现对线程的命名,具体代码如下:

    executor = Executors.newCachedThreadPool(new ThreadFactory() {

    final AtomicInteger threadNumber = new AtomicInteger(1);

    public Thread newThread(Runnable runnable) {
    // Use our own naming scheme for the threads.
    Thread thread = new Thread(Thread.currentThread().getThreadGroup(), runnable,
    "pool-spark" + threadNumber.getAndIncrement(), 0); //这里实现命名
    // Make workers daemon threads.
    thread.setDaemon(true);
    if (thread.getPriority() != Thread.NORM_PRIORITY) {
    thread.setPriority(Thread.NORM_PRIORITY);
    }
    return thread;
    }
    });

  • 相关阅读:
    Python基础
    pip install psycopg2出现python setup.py egg_info failed with error code 1 in /tmp/pip-build-YtLeN3/psycopg2错误处理
    Python基础
    C语言基础
    benchmarks
    用 MuGo 搭建 Go Engine 在 KGS 对战
    GPU
    linux 杀掉僵尸进程 (zombie process, defunct)
    CMakeLists.txt 语法
    软件列表(按字母排序)
  • 原文地址:https://www.cnblogs.com/mabiao008/p/10253745.html
Copyright © 2020-2023  润新知