线程池及Executor框架

为什么要使用线程池?

    诸如web服务器、数据库服务器、文件服务器或邮件服务之类的许多服务器应用程序都面向处理来自远程的大量短小的任务。请求以某种方式到达服务器,这种方式可以通过网络协议(HTTP、FTP)通过JMS队列或者可能通过轮询数据库。不管请求如何到达,服务器应用程序经常出现的情况是:单个任务处理的时间很短而请求的数目却巨大。如果每一个请求到达就创建一个新的线程,然后在新线程中处理请求,这样频繁的创建线程、销毁线程对系统的开销是非常大的。

    线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到多个任务上。优点是,1、在请求到达时线程已经存在,所以无意中消除了线程创建带来的时间延迟。这样就可以立即处理请求,减少应用的响应时间。2、通过适当的调整线程池中的线程数目,也就是当请求的数目超过某个阀值时,就强制其他任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

风险与机遇

    用线程池构建应用程序容易遭受任何其他多线程应用程序容易遭受的并发风险,诸如同步错误和死锁,还容易遭受特定于线程池的少量风险,诸如与池有关的死锁、资源不足和线程泄漏。

Future与Callable、FutureTask

    Callable与Runnable功能相似,Callable的call有返回值,可以返回给客户端,而Runable没有返回值,一般情况下,Callable与FutureTask一起使用,或者通过线程池的submit方法返回相应的Future。

   Future就是对具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。get方法阻塞,直到任务返回结果。

  FutureTask则是一个RunnableFuture,而RunnableFuture实现了Runnable又实现了Future这两个接口。

线程池的核心组成部分及其运行机制

    corePoolSize:核心线程池大小 cSize

    maximumPoolSize:线程池最大容量 mSize

    keepAliveTime:当线程数量大于核心时,多余的空闲线程在终止之前等待新任务的最大时间。

    unit:时间单位

    workQueue:工作队列 nworks

    ThreadFactory:线程工厂

    handler:拒绝策略

运行机制

    通过new创建线程池时,除非调用prestartAllCoreThread方法初始化核心线程,否则此时线程池中有0个线程,即时工作队列中存在多个任务,同样不会执行。

    任务数X

    x<=cSize 只启动x个线程

    x>=cSize && x<nWork +cSize  会启动<=cSize 个线程 其他任务就放在工作队列里

   当  x > cSize && x > nWork+cSize 时

        x-(nworks) <= mSize 会启动x-(nworks) 个线程

        x-(nworks) > mSize 会启动mSize个线程来执行任务,其余的执行相应的拒绝策略    

线程池拒绝策略

   AbortPolicy:该策略直接抛出异常,阻止系统正常工作。

   CallerRunsPolicy:只要线程没有关闭,该策略直接在调用线程执行当前被丢弃的任务。

   DiscardPolicy:什么事都不做,直接把任务丢弃。

   DiscardOldestPolicy:丢弃最老的一个请求(任务队列里面的第一个)

import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorDemo {

    public static void main(String[] args) {
        LinkedBlockingQueue<Runnable> linkedBlockingQueue = new LinkedBlockingQueue<>(20);
        ThreadPoolExecutor threadPoolExecutor  = new ThreadPoolExecutor(10,20,3L, TimeUnit.SECONDS,linkedBlockingQueue);
        Future<?> future = null;
        for (int i = 0;i<40;i++){
               threadPoolExecutor.submit(()->{

                    try {
                        Thread.sleep(2000L);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                });

            System.out.println(threadPoolExecutor.getActiveCount());
        }
        threadPoolExecutor.prestartAllCoreThreads();

    }
}

运行结果

通过修改代码中的初始化参数可以验证上面的理论。

Executor框架

   通过相应的方法,能创建出6种线程池。

ExecutorService executorService = Executors.newCachedThreadPool();

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);

ExecutorService workStealingPool = Executors.newWorkStealingPool();

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();

newCachedThreadPool:创建一个可以根据需要 创建新线程的线程池,如有空闲线程,优先使用空闲的线程。

newFixedThreadPool:创建一个固定大小的线程池,在任何时候,最多只有N个线程在处理任务

newScheduledThreadPool:能延迟执行、定时执行的线程池。

newWorkStealingPool:工作窃取,使用多个队列来减少竞争。

newSingleThreadExecutor:单一线程的线程池,只是用唯一一个线程来执行任务,即时提交再多的任务,也都是会放在等待队列。

线程池的使用建议

   尽量避免使用Executor框架创建线程池

      newFixedThreadPool newSingleThreadExecutor 

      允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

      newCachedThreadPool newScheduledThreadPool

      允许的创建线程数量为 Integer.MAX_VALUE,可能就会创建大量的线程,从而导致OOM

      为什么第二个例子,在限定的堆的内存之后,还会把整个电脑的内存撑爆

            创建线程池时,核心线程数不要过大

            相应的逻辑,发现异常时要时常处理

            submit 如果发生异常,不会立即抛出,而是在get的时候,再抛异常。

            execute 直接抛出异常。

猜你喜欢

转载自blog.csdn.net/sinat_36265222/article/details/86706006