Java线程池

简述

在面向软件编程中,创建和销毁对象是一件非常耗时的事情,因为创建一个对象要获取内存资源或者其它更多的资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能在对象销毁时进行回收。所以提供程序效率的方法就是减少对象的创建和销毁。如何利用已有的对象来服务就是一个需要解决的问题。

Java线程池实现了一个Java高并发的、Java多线程的、可管理的统一调度器。java.util.concurrent.Executors工作中最常用的和熟知的,顺便一提,java.util.concurrent包由出自著名的大神Doug Lea之手。

Executors是个线程工厂类,方便快速地创建很多线程池,也可以说是一个线程池工具类。配置一个线程池是比较复杂的,尤其是对于线程池原理不是很清楚的情况下,很可能配置的线程池不是最优的,以至于达不到预期的效果,因此Executors类里面给我们提供了一些静态工厂方法,以便我们能生成常用的线程池。
常用的方法有如下三种:

  • 1、newSingleThreadExecutor:创建一个单线程的线程池。
  • 2、newFixedThreadPool:创建固定大小的线程池。
  • 3、newCachedThreadPool:创建一个可缓存的线程池。
1、newSingleThreadExecutor的使用

创建一个单线程的线程池。这个线程只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常而结束,那么会有一个新的线程来代替它。此线程保证所有的任务的执行顺序按照任务的提交顺序执行。

public class SingleThreadExecutorDemo {
    public static void main(String[] args) {
        ExecutorService executorService=Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int no=i;
            Runnable task=new Runnable() {
                
                @Override
                public void run() {
                    try{
                        System.out.println("into "+no);
                        Thread.sleep(1000L);
                        System.out.println("end "+no);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            };
            //交由线程池处理任务
            executorService.execute(task);
        }
        executorService.shutdown();
        System.out.println("main thread have terminate");
    }   
}

运行结果如下所示:

Java线程池_第1张图片
newSingleThreadExecutor运行结果

从截图中可以得知任务是一条一条的执行的。

查看一下Executors.newSingleThreadExecutor()的实现方法:

    /**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * newFixedThreadPool(1) the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }

通过源码得知,它是调用了ThreadPoolExecutor创建了一个LinkedBlockingQueue的一个大小的线程池,采用默认的异常策略。

2、newCachedThreadPool的使用

创建一个缓冲池大小可根据需要伸缩的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务而言,这些线程池通常可提供程序性能。调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有60s未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。
Demo:

public class CachedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService=Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            final int no=i;
            Runnable task=new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println("into "+no);
                        Thread.sleep(10001L);
                        System.out.println("end "+no);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            };
            executorService.execute(task);
        }
        System.out.println("main thread have terminate");
        executorService.shutdown();
    }
}   
Java线程池_第2张图片
newCachedThreadPool运行结果

从运行结果可知,任务一开始所有的线程就开始执行了,都在互相争夺CPU资源。

接下来我们看下它的源码实现:

    /**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to execute will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

代码创建了一个内核线程池。线程为零,来一个线程就在线程池里面创建一个SynchronousQueue。

3、newFixedThreadPool的使用

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数nThreads线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新的线程将代替它执行后续任务(如果需要)。在某个线程被显示关闭之前,池中的线程将一直存在。
Demo:

public class newFixedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService=Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            final int no=i;
            Runnable task=new Runnable() {
                @Override
                public void run() {
                    try{
                        System.out.println("into "+no);
                        Thread.sleep(1000L);
                        System.out.println("end "+no);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            };
            executorService.execute(task);
        }
        System.out.println("main thread have terminate");
        executorService.shutdown();
    }
}

运行结果如下:

Java线程池_第3张图片
newFixedThreadPool运行结果

从结果上面看,一下子只有5个线程同时执行,然后结束一个再执行一个。

接下来看下源码实现:

    /**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * nThreads threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());

代码创建了一个指定大小的LinkedBlockingQueue的线程池。

线程池的好处
1、合理利用线程能带来4个好处

(1). 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
(2). 提供响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
(3).提供线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是要做到合理地利用线程,必须对其原理了如指掌。
(4).防止服务器过载,形成内存溢出,或者CPU耗尽。

2、线程池技术如何高服务器程序的性能

这里所提及的服务器程序是指能够接收客户端请求并处理请求的程序,而不只是那些接受网络客户端请求的网络服务器程序。多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。但如果对多线程应用不当,会增加对单个任务的处理时间。

3、举个栗子

假设在一台服务器完成一项任务的时间为T,并假设:

  • T1,创建线程的时间。

  • T2,在线程中执行任务所花费的时间,包括线程同步所需时间

  • T3,线程销毁时间
    显然T=T1+T2+T3。这是一个极度简化的假设栗子。
    可以看出T1、T3是多线程本身带来的开销,我们渴望减少T1、T3所用的时间从而减少T的时间。但一些线程的使用者并没有注意到这一点,所以在程序中频繁的创建和销毁线程,这导致T1和T3在T中占有相当比例。显然这是突出了线程的弱点(T1、T3),而不是优点(并发性)。

            线程池技术正是关注如何缩短调整T1、T3时间的技术,从而提高服务器程序性能的。它把T1、T3分别安排在服务器程序启动或结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户端请求时,不会有T1、T3的开销了。
    

线程池不仅调整了T1、T3产生的时间段,而且它还显著减少了创建线程的数目。再看一个栗子:

假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程数目完成。我们比较一下利用线程技术和不利用线程池技术的服务器处理这些请求时所产生的线程总数。在线程池中,线程数一般是固定的,所以产生线程的总数不会超过线程池中的数目或者上限(以下简称线程池尺寸),而如果服务器不利用线程池来处理这些请求则线程总数为50000.一般线程池尺寸是远远小于50000的。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高整体效率。

4、线程池应用范围

(1)需要大量的线程来完成任务,且完成任务的时间比较短。Web服务器完成网页请求这样的任务,使用多线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet回话时间比线程的创建时间打多了。
(2)对性能要求苛刻的应用,比如要求服务器迅速响应客户端请求。
(3)接收突发性的大量请求,但不至于使用服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池的情况下,将产生大量的线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现OutOfMemory的错误。

The end: next: 线程池工作机制与原理

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