深入线程池

Posted this.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入线程池相关的知识,希望对你有一定的参考价值。

之前在读Java进阶书籍的时候,接触到了关于线程池的使用,但是一直没有进行系统的理解。这篇博客主要是对线程池功能的梳理。
使用线程池来执行任务相对于线程来讲有许多优点:
1.能够重用线程池里的线程,减少创建线程的开销。
2.可以控制线程池中的最大并发数。
3.可以对线程进行简单的管理。

线程池的简单使用

public class Test 
    public static void main(String[] args) 
        ExecutorService executor = Executors.newFixedThreadPool(3);
        executor.execute(new MyThread());
        executor.execute(new MyThread());
        executor.execute(new MyThread());
        executor.shutdown();
    

    static class MyThread extends Thread 
        @Override
        public void run() 
            System.out.println(Thread.currentThread().getName() + ":running");
        
    

运行后,每个线程就会执行起来:

pool-1-thread-1run
pool-1-thread-3run
pool-1-thread-2run

上述的示例是通过Executors.newFixedThreadPool这个方法来生成一个线程池,当然,在编码的时候会发现Executors不仅仅只有newFixedThreadPool这个方法,还有其他的方法来生成线程池,那它们之前有什么区别?
在了解其中的区别之前,需要先了解一个线程池真正是在哪里创建的,查看newFixedThreadPool的源码:

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

上述是通过创建了ThreadPoolExecutor对象来创建线程池。其中,该类的构造方法所需的参数需要了解一下:

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

corePoolSize:线程池中核心线程的数量,核心线程会一直存活。但是可以把该类中的allowsCoreThreadTimeOut方法的参数传入true以及设定keepAliveTime的时间来终止线程。意味着当核心线程的空闲时间超出keepAliveTime时也会被终止。如果线程池中核心线程未到达指定的数量,新加的任务就会直接启动一条核心线程来执行。
maximumPoolSize:线程池中线程的最大数量。等于核心线程+非核心线程。
keepAliveTime:线程空闲的最大时间,超出时间,非核心线程就会被终止。
unit:指定keepAliveTime的时间单位。
workQueue:工作队列,通过execute方法可以往队列添加线程任务。
threadFactory:用于创建新线程。

有了上述知识的基础后,再继续分析各种线程池的区别:

方法:newFixedThreadPool

该方式会产生一个线程数量固定的线程池,方法的参数即为线程数量。池内的线程处于空闲状态时,不会被回收。当线程池内线程数量达到设定的参数时,新来的任务就会被阻塞,直到有线程执行完毕。

```
public static ExecutorService newFixedThreadPool(int nThreads) 
        //核心线程数量和最大线程数量,说明该线程池中所有的线程都是核心线程。另外,超时时间为0,即没有超时机制。
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
 

方法:newCachedThreadPool

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

由上面的参数可以知道,该线程池中没有核心线程,而且该线程池中线程的数量不固定,最大线程数也相当大。当线程池中有空闲的线程时,就会被用来执行新任务,否则,超时60s后就会被终止。这类线程池用于执行大量且耗时小的任务。

方法:newSingleThreadExecutor

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

可以看到该线程池中只有一条线程,意味着每次只能执行一条线程,并且需要等该线程执行完毕后才能继续执行下一条线程。这种情况下就可以不用考虑并发导致数据冲突错误的发生。

方法:newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
        return new ScheduledThreadPoolExecutor(corePoolSize);
    

public ScheduledThreadPoolExecutor(int corePoolSize) 
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    

该线程池核心线程数量固定,非核心线程数量没有限制。当非核心线程空闲时会立即被回收。该线程池可以通过调用schedule方法来设定执行线程的延时。可用于执行周期性,定时的任务。

关于AsyncTask中的线程池

AsyncTask是一个轻量级的异步执行任务的框架。里面也封装了线程池用于处理多个线程的异步任务。那AsyncTask里的线程池是如何被定义的?

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE = 1;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() 
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) 
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        
    ;

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);


    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

可以在最后一个方法中明显地看到该线程池的配置:

CORE_POOL_SIZE:CPU核心数+1,核心线程数量。
MAXIMUM_POOL_SIZE:最大线程数量为CPU核心数的2倍+1
KEEP_ALIVE :线程池核心线程无超时机制,非核心超时时间为1秒
LinkedBlockingQueue(128):任务队列容量为128

以上是关于深入线程池的主要内容,如果未能解决你的问题,请参考以下文章

在使用线程池时应特别注意对ThreadLocal的使用

Java Review - 创建线程和线程池时建议指定与业务相关的名称

Java Review - 创建线程和线程池时建议指定与业务相关的名称

当一个线程 execve() 一个文件时会发生啥?

当一个线程试图访问一个互斥锁资源时会发生啥?

调用具有条件变量等待的线程对象的析构函数时会发生啥?