线程池原理及应用

六月ゝ 毕业季﹏ 提交于 2020-02-29 01:37:35

一、线程池的创建和常用参数分析
先看一个线程池的创建

 private static void testThreadPool(){
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<?> future = executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " running");
        });
    }

翻看newFixedThreadPool()的源码可以看到最终执行的时候有以下几个参数:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        ...
    }

a) corePoolSize 核心线程数,保持在线程池中线程的数量
b) maxinumPoolSize 线程池允许的最大线程数
c) keepAliveTime/timeUnit 线程池中线程空闲不被释放的最大时间,配合timeUnit使用,为0表示永远不被释放。
d) workQuene 线程任务阻塞队列
e) threadFactory 线程池创建工厂
f) handler(RejectedExecutionHandler) 当workQuene无法存放新任务的时候,或添加新任务后线程池停止工作,使用设置的拒绝策略拒绝添加新任务的执行,可以用rejectedExecution来实心自己的拒绝策略。默认拒绝策略:AbortPolicy,直接抛出异常。

二、常用线程池和执行过程
Java中提供的常用线程池有:
1)固定线程数量的线程池

ExecutorService executorService = Executors.newFixedThreadPool(1);

固定数量的线程。

2)单线程的线程池

ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();

单例的线程。

3)可缓存的线程池

ExecutorService executorServiceCache = Executors.newCachedThreadPool();

核心线程数为0,最大线程数无限大(实际为int最大;空闲线程可以缓存60秒,空闲超过60s的线程会被回收;使用了SynchronousQueue同步队列,添加任务的同时须有工作线程来取任务才完成任务的添加和执行。

4)定时执行的线程池

 ExecutorService executorServiceTime = Executors.newScheduledThreadPool(1);

执行过程
submit()和execute()都是 ExecutorService 的方法,是添加线程到线程池中,execute()没有返回值。
submit()有返回值,返回future,1)可以执行cancle方法取消执行 。2)可以通过get()方法判断是否执行状态。

三、线程池常用队列LinkedBlockingQueue

继承关系
原理部分不再详述。使用参考文档LinkedBlockingQueue使用

三、可定时执行的线程池
ScheduledExecutorService 他的原理是线程池工作线程+时间任务队列。
ScheduledExecutorService在设计之初就是为了解决Timer&TimerTask的单线程问题。因为天生就是基于多线程机制,所以任务之间不会相互影响(只要线程数足够。当线程数不足时,有些任务会复用同一个线程)。

除此之外,因为其内部使用的延迟队列,本身就是基于等待/唤醒机制实现的,所以CPU并不会一直繁忙。同时,多线程带来的CPU资源复用也能极大地提升性能。

它支持四个方法:

/**
 * 带延迟时间的调度,只执行一次
 * 调度之后可通过Future.get()阻塞直至任务执行完毕
 */
1. public ScheduledFuture<?> schedule(Runnable command,
                                      long delay, TimeUnit unit);

/**
 * 带延迟时间的调度,只执行一次
 * 调度之后可通过Future.get()阻塞直至任务执行完毕,并且可以获取执行结果
 */
2. public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                          long delay, TimeUnit unit);

/**
 * 带延迟时间的调度,循环执行,固定频率
 */
3. public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                 long initialDelay,
                                                 long period,
                                                 TimeUnit unit);

/**
 * 带延迟时间的调度,循环执行,固定延迟
 */
4. public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                    long initialDelay,
                                                    long delay,
                                                    TimeUnit unit);

1、schedule Runnable
该方法用于带延迟时间的调度,只执行一次。调度之后可通过Future.get()阻塞直至任务执行完毕。我们来看一个例子。

@Test public void test_schedule4Runnable() throws Exception {
    ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
    ScheduledFuture future = service.schedule(() -> {
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task finish time: " + format(System.currentTimeMillis()));
    }, 1000, TimeUnit.MILLISECONDS);
    System.out.println("schedule finish time: " + format(System.currentTimeMillis()));

    System.out.println("Runnable future's result is: " + future.get() +
                       ", and time is: " + format(System.currentTimeMillis()));
}

上述代码达到的效果应该是这样的:延迟执行时间为1秒,任务执行3秒,任务只执行一次,同时通过Future.get()阻塞直至任务执行完毕。

2、schedule Callable
和Runnable基本相同,唯一的区别在于future.get()能拿到Callable返回的真实结果。

3、scheduleAtFixedRate
该方法用于固定频率地对一个任务循环执行,我们通过一个例子来看看效果。

@Test public void test_scheduleAtFixedRate() {
    ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
    service.scheduleAtFixedRate(() -> {
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task finish time: " + format(System.currentTimeMillis()));
    }, 1000L, 1000L, TimeUnit.MILLISECONDS);

    System.out.println("schedule finish time: " + format(System.currentTimeMillis()));
    while (true) {
    }
}

4、scheduleWithFixedDelay

@Test public void test_scheduleWithFixedDelay() {
    ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
    service.scheduleWithFixedDelay(() -> {
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task finish time: " + format(System.currentTimeMillis()));
    }, 1000L, 1000L, TimeUnit.MILLISECONDS);

    System.out.println("schedule finish time: " + format(System.currentTimeMillis()));
    while (true) {
    }
}

直白地讲,scheduleAtFixedRate()为固定频率,scheduleWithFixedDelay()为固定延迟。固定频率是相对于任务执行的开始时间,而固定延迟是相对于任务执行的结束时间,这就是他们最根本的区别!

最后给出一个案例,使用future获取异步结果的用法
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!