Java线程池

1.  线程池分类

为了避免重复创建和销毁线程而导致额外的性能开销,JDK 提供了线程池功能来实现线程的复用,具体分为以下几类:

newFixedThreadPool():该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新任务提交时,如果线程池中存在空闲的线程,则立即执行;如果没有,则新任务会被暂时存在一个任务队列中,待有线程空闲时再进行处理。

newSingleThreadExecutor(): 该方法返回一个只有一个线程的线程池。若多个任务被提交到该线程池,则多余的任务会被保存在一个任务队列中,待线程空闲,按照先入先出的顺序被执行。

newCachedThreadPool():根据实际情况动态调整线程数量。当新任务提交时,会优先复用空闲的线程;如果所有线程均处于工作状态,则会创建新的线程来进行处理。

newSingleThreadScheduledExecutor():该方法返回一个 ScheduledExecutorService 对象,线程池大小为 1 。SeheduledExectorService 在继承 ExecutorService 的基础上还额外支持定时任务的执行。

newScheduledThreadPool():与 newSingleThreadScheduledExecutor 方法类似,但可以指定线程池中线程的数量。

线程池的基本使用如下:

public class J1_ThreadPool {

    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + "正在执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            // 提交任务到线程池
            executorService.submit(new Task());
        }
        // 关闭线程池,此时不再接受新任务,但仍会等待原有的任务执行完成,如果想要立即关闭,则可以使用shutdownNow()
        executorService.shutdown();
    }
}

2. 定时任务

上面线程池分类中的 newSingleThreadScheduledExecutor() 和 newScheduledThreadPool() 都可以用于创建支持定时任务的线程池,它们返回的都是 ScheduledExecutorService 接口的实例。ScheduledExecutorService 接口中定义了如下三类定时方法:

/*在给定的时间,对任务进行一次性调度*/
public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);

/
 * 以上一个任务开始执行时间为起点,等待period时间后开始调度下一次任务,
 * 如果任务耗时大于period,则上一次任务结束后立即执行下一次任务
 */
public ScheduledFuture scheduleAtFixedRate(Runnable command,long initialDelay,long period,
                                              TimeUnit unit);
/
 * 以上一个任务开始执行时间为起点再经过delay时间后开始调度下一次任务,
 * 不论任务耗时如何,上一次任务结束后都需要等待delay时间之后才可以执行下一次任务
 */
public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay,long delay,TimeUnit unit);

使用示例如下:

public class J2_ScheduledTask {

    private static long cacheTime = System.currentTimeMillis();

    static class Task implements Runnable {

        private String type;

        Task(String type) {
            this.type = type;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                long nowTime = System.currentTimeMillis();
                System.out.println(type + Thread.currentThread().getId() + "执行耗时" + (nowTime - cacheTime) + "毫秒");
                cacheTime = nowTime;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // 为避免相互间的影响,以下各种场景最好分别测试:
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);
        // 任务只会被执行一次
        pool.schedule(new Task("schedule"), 2, TimeUnit.SECONDS);
        // 指定2秒为固定周期执行,因为项目执行耗时5秒,此时项目结束会立马执行下一次任务,所以输出的时间间隔为5秒
        pool.scheduleAtFixedRate(new Task("FixedRate"), 0, 2, TimeUnit.SECONDS);
        // 总是在上一次项目结束后间隔指定周期执行,因为项目耗时5秒,还需要间隔2秒执行,所以输出的时间间隔为7秒
        pool.scheduleWithFixedDelay(new Task("WithFixedDelay"), 0, 2, TimeUnit.SECONDS);
        // pool.shutdown();
    }
}

3  线程池内部实现

不管是使用 newFixedThreadPool() 还是使用 newCachedThreadPool() 来创建线程池,其最终调用的都是 ThreadPoolExecutor 的构造器,定义如下:

public ThreadPoolExecutor(int corePoolSize,                      //核心线程数量
                          int maximumPoolSize,                   //最大线程数量   
                          long keepAliveTime,                    //超过核心线程数的线程的存活时间
                          TimeUnit unit,                       //存活时间的单位                            
                          BlockingQueue<Runnable> workQueue,     //任务队列
                          ThreadFactory threadFactory,           //线程工厂
                          RejectedExecutionHandler handler)      //拒绝策略

3.1 线程工厂

ThreadFactory 用于指定线程的创建方式,示例如下:

new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        // 将所有线程都设置为守护线程
        thread.setDaemon(true);
        System.out.println("create" + thread.getName());
        return thread;
    }
}

3.2 拒绝策略

当线程池中可用线程的数量为 0,并且等待队列已满的情况下,线程池需要按照 RejectedExecutionHandler 指定的拒绝策略来决定如何处理后续提交任务,JDK 中默认提供了以下四种拒绝策略:

ThreadPoolExecutor.AbortPolicy:直接拒绝新提交的任务,并抛出异常;
ThreadPoolExecutor.DiscardPolicy:静默拒绝新提交的任务,并不抛出异常;
ThreadPoolExecutor.DiscardOldestPolicy:丢弃等待时间最长的任务,然后再尝试执行新提交的任务;
ThreadPoolExecutor.CallerRunsPolicy:直接在调用 execute 方法的线程中运行新提交的任务。

4.线程池扩展

ThreadPoolExecutor 除了提供丰富的参数来满足多样化的需求外,还支持重载其生命周期方法来进行更加灵活的扩展:

ExecutorService executorService = new ThreadPoolExecutor(10, 20, 0L, TimeUnit.MILLISECONDS,
                                                         new LinkedBlockingQueue<>()) {
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.println("线程" + t.getName() + "准备执行");
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println("线程" + r + "执行结束");
    }

    @Override
    protected void terminated() {
        System.out.println("线程池退出");
    }
};

5.线程池大小

线程池的大小可以通过以下公式进行估算:

Ncpu = CPU的数量

Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1

W/C = 等待时间与计算时间的比率

为保证处理器达到期望的使用率,最优的线程池的大小等于:

Nthreads = Ncpu x Ucpu x (1+W/C)

你可能感兴趣的:(Java,java,开发语言)