Java线程池

线程池概述

通俗的讲,线程池就是一个池子,里面全是线程。目的是对线程进行统一管理,对线程进行复用,对线程数量进行控制,避免过多的线程导致系统缓慢。和线程池紧密关联的是阻塞队列,当线程池中线程全部属于活跃状态时,新进来的请求就需要放在阻塞队列中进行排队等待空闲的线程。

Java中线程池的顶级接口Executor:

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

传入一个Runnable对象,并进行执行。

Executors

Java中Executors类提供了许多便捷的方式来创建线程池。

FixedThreadPool:固定线程池。每当提交一个任务就创建一个线程并放入线程池,直到线程池中线程到达最大值,线程池不再变化,线程除非发生了错误不会被回收。当线程池中线程全部处于活跃状态时,有新的获取线程请求,会将其放入LinkedBlockingQueue阻塞队列中,其中阻塞队列无限大。初始化方法如下。

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

CachedThreadPool:缓存线程池。如果请求线程池中有空闲线程就分配空闲线程,否则创建一个新的线程并放入线程池,线程池无上限(最大值为Integer最大值2147483647),当一个线程空闲时间超过60S就会被回收。初始化方法如下。

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

SingleThreadExecutor:单个线程的线程池。池中只有一个线程,可以用来执行一些有顺序要求的任务(串行执行),当池中县城发生错误时,会创建一个新的线程替代。初始化方法如下。

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

ScheduledThreadPool:定时任务线程池。创建一个固定长度线程池,并且可以以定时任务的方式运行任务。初始化方法如下。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
	return new ScheduledThreadPoolExecutor(corePoolSize);
}

SingleThreadScheduledExecutor:SingleThreadExecutor+ScheduledThreadPool。

ThreadPoolExecutor

我们可以发现,虽然Executors的FixedThreadPool和CachedThreadPool方法返回的都是一个ExecutorService,但是实际上都是一个ThreadPoolExecutor,实际上我们在使用线程池的时候,更加的推荐使用ThreadPoolExecutor,而不是使用Executors,使用ThreadPoolExecutor更加的灵活,方便定制化自己所需要的线程池,也能让自己对线程池的使用更加的透明。

1.ThreadPoolExecutor的最完全构造方法。

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

corePoolSize:线程池中核心线程大小,默认情况下,如果线程池中的线程数量小于等于corePoolSize,则就算线程超过了keepAliveTime也不会被回收。当一个线程池很少被使用到时,可以使用ThreadPoolExecutor.allowCoreThreadTimeOut(true),来开启超时回收核心线程。

maximumPoolSize:线程池最大大小。

keepAliveTime:线程池中线程最大空闲时间(线程空闲时间达到这个值,会被回收),结合TimeUnit使用。

unit:keepAliveTime的时间单位。

workQueue:线程池的阻塞队列(线程池中线程全部处于活跃状态时,新的任务会进入阻塞队列),通常的实现有LinkedBlockingQueue和ArrayBlockingQueue等。

threadFactory:线程工厂,创建线程的工厂,比如我们想要线程池中的线程命名定制化,就需要自己实现一个ThreadFactory。给上一个简单的NamedThreadFactory实现。

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class NamedThreadFactory implements ThreadFactory {

    private static final AtomicInteger TOTAL_THREAD_NUMBER = new AtomicInteger(1);
    private final AtomicInteger thisThreadNumber;
    private final String threadPrefix;

    public NamedThreadFactory() {
        this("pool-" + TOTAL_THREAD_NUMBER.getAndIncrement());
    }

    public NamedThreadFactory(String prefix) {
        this.thisThreadNumber = new AtomicInteger(1);
        this.threadPrefix = prefix + "-thread-";
    }

    @Override
    public Thread newThread(Runnable runnable) {
        String name = this.threadPrefix + this.thisThreadNumber.getAndIncrement();
        return new Thread(runnable, name);
    }

}

handler:饱和策略,决定了,当阻塞队列被任务堆满时,新进来的任务会被如何处置。ThreadPoolExecutor提供了四种RejectedExecutionHandler的实现。

  • ThreadPoolExecutor.CallerRunsPolicy:即caller runs,当阻塞队列满了的时候,其他线程调用execute()方法不再使用线程池,而是直接在主线程(调用线程)执行。
  • ThreadPoolExecutor.AbortPolicy:即abort,当阻塞队列满了的时候,直接抛出RejectedExecutionException异常,拒绝该任务。
  • ThreadPoolExecutor.DiscardPolicy:即discard,当阻塞队列满了的时候,舍弃新的任务(直接忽略新的execute()),与AbortPolicy不同的是不会抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy:即DiscardOldest,当阻塞队列满了的时候,舍弃阻塞队列中最早的任务,再将此任务放入队列。

如果上述几种策略不满足系统的需求,可以自己实现RejectedExecutionHandler接口创建自定义的策略。

2.ThreadPoolExecutor的构造后修改

ThreadPoolExecutor中除了BlockingQueue,其他的都可以通过setXXX方法,在ThreadPoolExecutor构造之后进行修改,如ThreadPoolExecutor.setCorePoolSize(int size);可以修改线程池核心线程大小。

3.关于SingleThreadExecutor

我们可以看到其实SingleThreadExecutor的创建中的FinalizableDelegatedExecutorService类中传入了new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

我们查看源码可以发现,FinalizableDelegatedExecutorService中是包装设计模式包装了ExecutorService,也就是说方法的最后执行者还是ThreadPoolExecutor,这一层包装的目的是不让这个线程池后期进行修改,因为我们上一节讲到ThreadPoolExecutor的构造中的参数都可以后期通过setXXX进行修改。

总的来说new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());和Executors.newSingleThreadExecutor的唯一区别就是,前者可以通过setXXX方式修改变成非单一线程的线程池,而后者无法进行修改。


关于ScheduledThreadPoolExecutor,暂时不进行介绍。上述内容参考《Java并发编程实战》,有兴趣的朋友可以去阅读一下这本书籍。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值