Java多线程之线程复用 线程池

线程池为什么会出现?

1. 提高利用率,没有线程池时,当每次有任务时就新建一个线程,当任务结束时线程销毁,当任务的执行时间远远小于线程的创建和销毁的时间时,就显得效率低,得不偿失。2. 可以控制线程的数量防止内存溢出

什么是线程池

线程池就是多个线程的集合,简单理解就是一个能盛有线程的容器,当有任务需要执行时。就从线程池中拿出空闲线程完成任务,任务结束后就将该线程线程放入线程池中。

jdk对线程池的支持

jdk提供了一套Executor框架,本质就是一个线程池。

核心成员图 

ThreadPoolExrcutor就是一个线程池。为什么要说它呢,因为Executor提供的各种类型的线程池都是依赖它创建的,看一下它的构造方法。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数分析: 

   /**
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
  */

corePoolSize:线程池中存活的线程数。
maximumPoolSize:线程池中能允许存活的最大线程数。
keepAliveTime:当线程池的线程数超过的corePoolSize数量的线程在空闲时间下允许存活的时间。
unit:时间单位
workQueue:任务队列的类型,当当前活跃线程达到maximumPoolSize时,任务等待队列。
threadFactory:线程工厂,用于创建线程。
handler:超负载时,超负载的意思是等待队列慢的时候的执行策略

Executors是 线程池工厂,通过静态方法来获得各种类型的线程池,主要的各种类型线程池的构造方法。

也可以看出他们的都是由ThreadPoolExecutor创建。

            public static ExecutorService newFixedThreadPool(int nThreads) {
	        return new ThreadPoolExecutor(nThreads, nThreads,
	                                      0L, TimeUnit.MILLISECONDS,
	                                      new LinkedBlockingQueue<Runnable>());
	        }
		
		
		public static ExecutorService newSingleThreadExecutor() {
		    return new FinalizableDelegatedExecutorService
		        (new ThreadPoolExecutor(1, 1,
		                                0L, TimeUnit.MILLISECONDS,
		                                new LinkedBlockingQueue<Runnable>()));
		}
		
		
		
		public static ExecutorService newCachedThreadPool() {
		    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
		                                  60L, TimeUnit.SECONDS,
		                                  new SynchronousQueue<Runnable>());
		}
		
		public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
                    return new ScheduledThreadPoolExecutor(corePoolSize);
                }


		public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
		    return new DelegatedScheduledExecutorService
		        (new ScheduledThreadPoolExecutor(1));
		}

newFixedThreadExecutor(): 返回具有固定线程的线程池,有新任务需要执行时,当线程池中存在空闲线程时就去执行,否则将任务放在任务队列中,从参数看一看出时LinkedBlockingQueue,是一种无边界的队列,当线程池中有空闲线程时,就去处理任务队列的任务。

newSingleThreadExecutor():返回只有一个线程的线程池,每次只能有一个任务被执行,其他的任务在任务队列中等待,线程空闲时,执行任务队列的任务。

newCachedThreadPool():返回一个可以根据实际情况调整线程池线程数量的线程池,构造方法第一个参数corePoolSize=0说明在线程池刚创建时,线程池中没有线程,当有任务提交时就会先看有没有可以复用的空闲线程,有就复用,没有就尝试去新建线程,如果线程已经达到最大值,就执行拒绝策略。

newScheduledThreadPool ():返回一个定时延时的线程池,可以延时执行任务,并且可以制定线程池的大小。

newSingleThreadScheduledExecutor():返回一个定时延时的线程池,可以延时执行任务,线程池只有一个线程。

线程池的内部实现

从上面可以看出各种类型的线程池是依赖ThreadPoolExecutor来创建的,再看 ThreadPoolExecutor的构造方法。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

说说 BlockingQueue<Runnable> workQueue和 RejectedExecutionHandler handler这两个参数。

BlockingQueue是任务队列

它是一个接口继承了queue接口,当任务需要执行但是没有空闲线城时,就会放进该任务队列中等待。

public interface BlockingQueue<E> extends Queue<E>

不同类型的任务队列都实现了BlockingQueue接口:

  1. SynchronousQueue队列是直接提交队列,它没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。也就是说提交的任务不会被存储,会直接提交到线程执行,如果线程池中没有空闲队列,就尝试新建线程,如果线程数量到达最大值,执行拒绝策略,因此使用该队列的线程池的maxinumPoolSize应该很大,否则很多任务将会被拒绝。
  2. ArrayBlockingQueue队列是有界的任务队列,当有新的任务提交时,如果有空闲线程就利用空闲线程,如果没有,尝试创建新线程,当目前的线程数量<corePoolSize时就成功创建线程,并且执行任务,当已经超过corePoolSize时,就将任务放入任务队列,任务队列没有达到容量时可以放入,当任务队列已经满时,就去尝试继续创建线程,如果没有达到maxinumPoolQueue时可以创建线程,大于时就执行拒绝策略。
  3. LinkedBlockingQueue 无界的任务队列,当有新的任务提交时,如果有空闲线程就利用空闲线程,如果没有,尝试创建新线程,当目前的线程数量<corePoolSize时就成功创建线程,并且执行任务,当已经超过corePoolSize时,就将任务放入任务队列,任务队列时无界的,前提是内存无限大,也就是说只要有任务被放入任务队列,总是被允许的。
  4. priorityBlockingQueue 优先级队列,任务队列中的任务是具有优先级的,优先级大的任务先被执行,而LinkedBlockingQueue和ArrayBlockingQueue都是先进先出的公平队列。

超负载怎能怎么办?据决策绝 

 

  1. CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); }} 
    这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。(开始我总不想丢弃任务的执行,但是对某些应用场景来讲,很有可能造成当前线程也被阻塞。如果所有线程都是不能执行的,很可能导致程序没法继续跑了。需要视业务情景而定吧。)
  2. AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException();} 
    这种策略直接抛出异常,丢弃任务。(jdk默认策略,队列满并线程满时直接拒绝添加新任务,并抛出异常,所以说有时候放弃也是一种勇气,为了保证后续任务的正常进行,丢弃一些也是可以接收的,记得做好记录)
  3. DiscardPolicy:不能执行的任务将被删除 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {} 
    这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。
  4. DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程) 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) {e.getQueue().poll();e.execute(r); }} 
    该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略需要适当小心。

线程池的线程哪里来?

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

这是一个线程工厂,调用Executors新建线程池时,都是通过调用ThreadPoolExcutor,参数有一个ThreadFactory threadPoolFactory 这个接口就是产生线程池里面线程的接口。传入一个Runnable的对象。

我们也可以通过此线程工厂封装自己的线程池。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值