Su warm small talk Java thread pool principle and implementation

I. Introduction thread pool

Thread pool is a multithreaded processing forms processing tasks will be added to the queue, and then start these tasks automatically when a thread is created. Thread pool threads are background threads. Each thread uses the default stack size to default priority run, and in multi-threaded unit. If a thread in managed code is idle (eg is waiting for an event), then insert the thread pool thread to all other auxiliary processor to keep busy. If all thread pool threads are always busy, but the queue contains pending work, the thread pool creates another worker thread after a period of time but the number of threads will never exceed the maximum. More than the maximum thread can queue up, but they have to wait until another thread to complete before starting.

Use the thread pool is to reduce the time of creation and destruction of threads, because creating an object to obtain memory resources to virtual machines to track each object, so that it can be garbage collected after the object is destroyed, it is an important means to improve the efficiency of the service program is to do possible reduction in the number of creating and destroying objects, especially some of the objects are very resource-intensive creation and destruction.

Second, part of

1, the thread pool manager (ThreadPoolManager): used to create and manage the thread pool. It includes creating a thread pool, thread pool is destroyed, adding new tasks;

2, the worker thread (WorkThread): threads in the pool only two states: an operational state and wait state

3, the task Interface (Task): each task must implement <returns a value of callable and no return value runnable>, for the worker thread to run scheduled tasks. It sets out the main entrance of the task. After finishing the task runs, the operating status of the task.

4, task queue (work queue): for storing task is not processed. It provides a buffering mechanism, generally BlockingQuene implementation class.

Third, the principle and implementation of the Java thread pool

There are two ways to create threads: Thread inherit or implement Runnable.

Thread implements Runnable interface. It provides an empty run () method. So whether it is inherited or achieved Thread Runnable, it should have its own run () method.

A thread is created, it exists. Call start () method begins execution (execution run () method). Call waiting or call wait to enter the sleep into dormancy, the successful implementation of the completion or sleep is interrupted or exception occurred during the execution and exit.

Add a task process thread pool:

We first start with the creation of the thread pool, Executors.newFixedThreadPool (2) represents the creation of a thread pool has two threads, the source code is as follows:

public class Executors {
    //生成一个最大为nThreads的线程池执行器
  public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
}

As used herein LinkedBlockingQueue task queue manager, all pending task will be placed in the column, it is noted that this queue is a single-ended queue blocking. Thread pool is set up, and it needs to run the thread in which the threads in the thread pool is created when submit for the first time to submit the task, as follows:

    public Future<?> submit(Runnable task) {
        //检查任务是否为null
        if (task == null) throw new NullPointerException();
        //把Runnable任务包装成具有返回值的任务对象,不过此时并没有执行,只是包装
        RunnableFuture<Object> ftask = newTaskFor(task, null);
        //执行此任务
        execute(ftask);
        //返回任务预期执行结果
        return ftask;
    }

The key here is the code execute method, which implements the three functions.

1, the number of threads to create enough work, no more than the maximum number of threads, and keep the thread is running or waiting state.

2, the task waiting to be processed into the task queue

3, taken out from the task execution queue

The key here is to create worker threads, a thread that is created by the new Thread way, but it is not our task to create a thread (although our mission to achieve the Runnable interface, but it just played a landmark role), but after packing Worker thread code is as follows:  

private final class Worker implements Runnable {
// 运行一次任务
    private void runTask(Runnable task) {
        /* 这里的task才是我们自定义实现Runnable接口的任务 */
        task.run();
        /* 该方法其它代码略 */
    }
    // 工作线程也是线程,必须实现run方法
    public void run() {
        try {
            Runnable task = firstTask;
            firstTask = null;
            while (task != null || (task = getTask()) != null) {
                runTask(task);
                task = null;
            }
        } finally {
            workerDone(this);
        }
    }
    // 任务队列中获得任务
    Runnable getTask() {
        /* 其它代码略 */
        for (;;) {
            return r = workQueue.take();
        }
    }
}

Here is a schematic code to delete a large number of resources to determine the conditions and lock. The execute method is initiated by a worker thread the Worker class, execution is our first task, then change the thread gets tasks from the task queue by getTask method before continuing the implementation, but the problem is the task queue is a BlockingQuene, is blocking, meaning that if the elements of the queue is 0, then waiting state is maintained until far into the task, we take a look at LinkedBlockingQuene method, as follows:  

    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            try {
                // 如果队列中的元素为0,则等待
                while (count.get() == 0)
                    notEmpty.await();
            } catch (InterruptedException ie) {
                notEmpty.signal(); // propagate to a non-interrupted thread
                throw ie;
            }
            // 等待状态结束,弹出头元素
            x = extract();
            c = count.getAndDecrement();
            // 如果队列数量还多于一个,唤醒其它线程
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        // 返回头元素
        return x;
    }

Analysis here, we understand the process of creating a thread pool: create a blocking queue to accommodate the task, to do enough to create a thread (not to exceed the number of licenses thread) during the first mission, and processing tasks, after each work obtained from the self-thread tasks column task until the number of tasks in the task queue for up to 0, this time, the thread in a wait state, once the task added to the queue, the worker thread for wakeup Jizhao processing, thread reusability.

Use the thread pool is to reduce the time of creation and destruction of threads, because creating an object to obtain memory resources to virtual machines to track each object, so that it can be garbage collected after the object is destroyed, it is an important means to improve the efficiency of the service program is to do possible reduction in the number of creating and destroying objects, especially some of the objects are very resource-intensive creation and destruction.
This multi-threaded applications is very helpful, such as our popular servlet container, processed each request is a thread, the thread pool without using technology, every request to re-create a new thread, which can lead to the performance of the system load increase, affecting the efficiency of decline, mainly very low, weak burst, who wrote the code for this, give me out, ha ha.

Fourth, timely select a different thread pool to achieve

Java thread pool classes and include ThreadPoolExecutor ScheduledThreadPoolExecutor class.

For simplicity, also provides Exceutors static class, it can generate a different thread pool directly to the actuator, such as a single-threaded actuator with actuator buffering function like.

In order to understand these actuators, we first look at ThreadPoolExecutor class, where it complicated constructors can well understand the role of the thread pool, the code is as follows:

// Java线程池的完整构造函数
public ThreadPoolExecutor(
  int corePoolSize, // 线程池长期维持的最小线程数,即使线程处于Idle状态,也不会回收。
  int maximumPoolSize, // 线程数的上限
  long keepAliveTime, // 线程最大生命周期。
  TimeUnit unit, //时间单位                                 
  BlockingQueue<Runnable> workQueue, //任务队列。当线程池中的线程都处于运行状态,而此时任务数量继续增加,则需要一个容器来容纳这些任务,这就是任务队列。
  ThreadFactory threadFactory, // 线程工厂。定义如何启动一个线程,可以设置线程名称,并且可以确认是否是后台线程等。
  RejectedExecutionHandler handler) // 拒绝任务处理器。由于超出线程数量和队列容量而对继续增加的任务进行处理的程序。

This is the most complete ThreadPoolExecutor constructor, constructors are other references to the constructor implementation.

Thread pool management is the process: First, create a thread pool, and then gradually increase the number of threads corePoolSize according to the number of tasks, if the task this time there is still an increase, then placed workQuene until workQuene packed up, and then continue to increase the number of pool (enhanced processing power), and ultimately achieving maximumPoolSize, that if there is an increase at this time tasks come in? This requires the handler to deal with, or discard task, or reject the new task, or crowding out existing tasks.

In the task queue and thread pool are saturated, but there is a thread in a wait (task processing is complete, no new job) status for more than keepAliveTime, the thread terminates, said the number of threads in the pool will be gradually reduced until the number corePoolSize.

After submitting a job to the thread pool, its main processing flow as shown below:

We can imagine the thread pool for such a scenario: In a production line, workshop requirement is corePoolSize can have the number of workers, but the production line just created, not much work, do not need so many people. With the increase in number of jobs, the number of workers is also increasing, up until the increased number of corePoolSize. At this point there is how to increase job to do it?

Easy to handle, the task queue, corePoolSize number of workers do not stop processing tasks, the newly added tasks according to certain rules stored in the barn (that is, in our workQuene), once the mission grow faster than the ability of workers to deal with, but also that is when the warehouses were full, the workshop will continue to recruit workers (that is, to expand the number of threads), until it reaches the number of workers maximumPoolSize, that if all maximumPoolSize workers during the processing, and the warehouse is saturated, how the new mission deal with it? This will throw a specialized agency of the handler called to deal with, and it is either discarded these new tasks, either ignoring or replace other tasks.

After a period of time, gradually reduce the number of tasks, resulting in a portion of the workers in Daigong state, in order to reduce costs (Java is to reduce resource consumption of the system), began to dismiss workers until keeping corePoolSize number of workers, at which point even no work, no longer dismissed workers (not reduce the number of threads in the pool), which is able to ensure fast processing time later a mission.

明白了线程池的概念,我们再来看看Executors提供的几个线程创建线程池的便捷方法:

① newSingleThreadExecutor:单线程池。

顾名思义就是一个池中只有一个线程在运行,该线程永不超时,而且由于是一个线程,当有多个任务需要处理时,会将它们放置到一个无界阻塞队列中逐个处理,它的实现代码如下:  

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

它的使用方法也很简单,下面是简单的示例:

public static void main(String[] args) throws ExecutionException,InterruptedException {
    // 创建单线程执行器
    ExecutorService es = Executors.newSingleThreadExecutor();
    // 执行一个任务
    Future<String> future = es.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return "";
        }
    });
    // 获得任务执行后的返回值
    System.out.println("返回值:" + future.get());
    // 关闭执行器
    es.shutdown();
}

② newCachedThreadPool:缓冲功能的线程。

建立了一个线程池,而且线程数量是没有限制的(当然,不能超过Integer的最大值),新增一个任务即有一个线程处理,或者复用之前空闲的线程,或者重亲启动一个线程,但是一旦一个线程在60秒内一直处于等待状态时(也就是一分钟无事可做),则会被终止,其源码如下: 

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

这里需要说明的是,任务队列使用了同步阻塞队列,这意味着向队列中加入一个元素,即可唤醒一个线程(新创建的线程或复用空闲线程来处理),这种队列已经没有队列深度的概念了。

③ newFixedThreadPool:固定线程数量的线程池。

在初始化时已经决定了线程的最大数量,若任务添加的能力超出了线程的处理能力,则建立阻塞队列容纳多余的任务,其源码如下: 

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

上面返回的是一个ThreadPoolExecutor,它的corePoolSize和maximumPoolSize是相等的,也就是说,最大线程数量为nThreads。如果任务增长的速度非常快,超过了LinkedBlockingQuene的最大容量(Integer的最大值),那此时会如何处理呢?会按照ThreadPoolExecutor默认的拒绝策略(默认是DiscardPolicy,直接丢弃)来处理。

以上三种线程池执行器都是ThreadPoolExecutor的简化版,目的是帮助开发人员屏蔽过得线程细节,简化多线程开发。当需要运行异步任务时,可以直接通过Executors获得一个线程池,然后运行任务,不需要关注ThreadPoolExecutor的一系列参数时什么含义。当然,有时候这三个线程不能满足要求,此时则可以直接操作ThreadPoolExecutor来实现复杂的多线程计算。

newSingleThreadExecutor、newCachedThreadPool、newFixedThreadPool是线程池的简化版,而ThreadPoolExecutor则是旗舰版___简化版容易操作,需要了解的知识相对少些,方便使用,而旗舰版功能齐全,适用面广,难以驾驭。

五、线程池的关闭

我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同,shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。

只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。

六、线程池的好处

1、降低资源消耗

通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

2、提高响应速度

3、提高线程的可管理性

线程时稀缺资源,如果无限的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。

七、线程池的应用范围

1、需要大量线程来完成任务,且完成任务的时间比较短

网页(http)请求这种任务,使用线程池技术是很合适的。由于单个任务小,而任务数量巨大,你能够想象一个热门站点的点击次数。 但对于长时间的任务,比方一个Telnet连接请求,线程池的长处就不明显了。由于Telnet会话时间比线程的创建时间大多了。

2、对性能要求苛刻的应用,比方要求server迅速响应客户请求。

3、接受突发性的大量请求,但不至于使server因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,尽管理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。

八、总结

线程池通过线程的复用减少了线程创建和销毁的开销,通过使用任务队列避免了线程的阻塞从而避免了线程调度和线程上下文切换的开销。

发布了110 篇原创文章 · 获赞 8 · 访问量 6943

Guess you like

Origin blog.csdn.net/guorui_java/article/details/104027004