多线程(五):线程池

一、网摘

程序的运行,其本质上,是对系统资源(CPU、内存、磁盘、网络等等)的使用。如何高效的使用这些资源是编程优化演进的一个方向。线程池就是一种对提高系统资源利用的优化手段。

在多个线程并发处理任务的情况下,如果每个线程只是执行一个时间很短的任务就结束,那么线程的创建和销毁会占用相当一部分系统资源。线程池就是在池中创建多个活跃的线程,当任务到来时,就分配若干线程去执行任务,任务执行完之后,线程不会销毁,而是放回到线程池中,等待执行新的任务。这样就避免了线程频繁的创建和销毁。

参考博客:

https://www.cnblogs.com/rinack/p/9888717.html 

https://www.cnblogs.com/dolphin0520/p/3932921.html

二、ThreadPoolExecutor

用于线程池创建的类为ThreadPoolExecutor,线程池具备何种功能/属性与构建线程池时传入的参数相关。在工厂类Executors中,有已经设计好的线程池,可以直接拿来使用。JDK推荐使用Executors工厂类来创建线程池(programmers are urged to use the more convenient {@link Executors} factory methods),但是如果要根据实际情况来自己设计线程池的话,还是要用ThreadPoolExecutor。

下面即从ThreadPoolExecutor入手来学习线程池。

类继承关系

ThreadPoolExecutor继承自抽象类AbstractExecutorService。

AbstractExecutorService实现自ExecutorService接口中的方法

ExecutorService接口继承Executor,Executor是An object that executes submitted {@link Runnable} tasks.(一个用来执行提交的任务的类),里面只有一个方法execute(Runnable),execute()是一个关键方法,用来执行线程任务。

文档说明

mark部分JDK文档中对ThreadPoolExecutor类的说明。

-corePoolSize & maximumPoolSize:

当一个新任务(Runnable target)被execute()方法提交执行,有以下两种情况:

如果线程数小于核心池(corePoolSize )大小,将会创建一个新线程来执行任务,即便其他线程处于空闲状态。

如果线程数大于核心池大小,但小于线程池最大容量(maximumPoolSize),只有当任务队列已经满员的时候才会创建新线程,否则存入队列等待执行。

如果corePoolSize和maximumPoolSize相同,就得到一个fixed-size thread pool。

-On-demand construction:按需构建

默认情况下,只有当任务到来,才会执行线程的创建和启动。但prestartCoreThread()和prestartAllCoreThreads()允许在构建线程池之初创建好线程。

-ThreadFacotry

ThreadFactory用于创建线程,默认情况下使用defaultThreadFactory来创建线程,所创建的线程都在同一ThreadGroup中,并且具有相同的优先级,均为no-daemon线程。

-Keep-alive times

如果当前线程池线程数多于corePoolSize,则超过corePoolSize线程数的线程空闲时间超过Keep-alive times就会被停止。如果为0,线程将一直存在。

默认情况下,Keep-alive times仅适用于多于corePoolSize的线程,allowCoreThreadTimeOut(boolean)方法可让keep-alive适用于包括core线程在内的所有线程。即所有线程只要空闲一定时间就会被终结。

-Queuing 队列

BlockingQueue被用于转换(transfer)或持有(hold)任务。源码中的transfer,参考SynchronousQueue,自己的理解其实是传递,SynchronousQueue只是一个中转,到达一个任务就把任务交给一个线程,本身不存储任务。

一般情况下,当新任务到来时,如果线程数小于corePoolSize,将会创建新线程,而非存入队列。

如果corePoolSize或大于corePoolSize的线程在运行,将会存入队列而非创建新线程。

如果任务不能被存入队列(队列已满或其他原因),将会创建一个新线程。但是,如果当前线程数已经达到maximumPoolSize,并且队列已满,这个任务会被拒绝。

队列的三种策略(strategies):

  1. Direct handoffs - SynchronousQueue
  2. Unbounded queues - LinkedBlockingQueue
  3. Bounded queues - ArrayBlockingQueue

Direct handoffs:

直接传递:SynchronousQueue,直接将任务传递给线程而不是持有(hold)他们,所以SynchronousQueue的大小永远为0。这种策略下,线程数会一直增长,如果当前没有线程可用,试图queue一个任务将会失败。所以Direct handoffs策略要求maximumPoolSizes为无限大(unbounded),这样才能避免拒绝一个任务。但maximumPoolSizes无限大,当任务到来时直接创建线程。当任务到达的平均速度比它们被处理的速度还要快时,就有可能出现无限的线程增长。

Unbounded queues:

无限大的队列:LinkedBlockingQueue,队列无限大,这样当任务到来时,如果核心线程数未达到corePoolSize,创建新线程,如果已经达到corePoolSize,任务会存入队列。此后线程数不会再增长,只会将任务一直存到这个无限大的队列中,也就是线程池最大线程数就是corePoolSize,maximumPoolSize无效。

这种策略适用于,传递的多个任务(tasks)之间是独立的、相互不影响的,适用于处理多个执行时间短、数量庞大的任务,当任务到达的平均速度比它们被处理的速度还要快时,就有可能出现队列无线的增长。

Bounded queues:

有限队列:ArrayBlockingQueue,当任务到来时,如果未达到corePoolSize,线程数增长;已达到corePoolSize未达到maximumPoolSize,存入任务队列,如果线程数已达到最大,且任务队列已满,拒绝新到来的任务。

有限队列配合合适大小的maximumPoolSize,使用容量大的队列(queue)和小的maximumPoolSize会使cpu消耗、系统资源(OS Resource)达到最小,但是可能会导致任务处理效率低(low throughput);使用较小队列需要较大的maximumPoolSize,这样能够充分利用CPU,但也许会导致无法接受的调度消耗(encounter unacceptable scheduling overhead),这样同样会降低任务处理效率。

-Rejected tasks 任务拒绝

当线程池已关闭,或是线程数已达maximumPoolSize、队列已满,execute()提交的任务会被拒绝。四种任务拒绝策略:

  • AbortPolicy,默认策略,任务被拒绝抛出RejectedExecutionException异常
  • CallerRunsPolicy,由execute()的caller线程执行此任务
  • DiscardPolicy,任务直接被丢弃,不会抛出异常
  • DiscardOldestPolicy:队列最前面的任务被丢弃,新的任务存入队列,如果有多个任务需要拒绝,重复此

这样洋洋洒洒看下来,线程池这一块还蛮复杂,所以JAVA开发者开发了Executors类,定义好了一些适用于大多数情况的连接池,让我们可以直接拿来使用,如此在使用线程池时,就不用自己费心去设计、考虑以上那些内容了。所以JAVA的优势不仅在于语法简单,更重要的是,其内部还为我们默默实现了很多内容,这些内容不只是语法上的,比如指针,还包括开发者需求方面,就比如Executors直接创建线程池。

构造方法

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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数说明:

  • corePoolSize,核心池大小,可以直接理解为线程池大小
  • maximumPoolSize,线程池最大容量
  • keepAliveTime,线程空闲状态下的存活时间,默认线程数大于corePoolSize小于maximumPoolSize生效
  • TimeUnit 时间单位
  • BlockingQueue<Runnable> workQueue,任务队列
  • ThreadFactory threadFactory,线程工厂
  • RejectedExecutionHandler handler,拒绝任务处理策略

ThreadPoolExecutor的使用

public class TestPool {
	public static void main(String[] args) {
		// corePoolSize:5;maximumPoolSize:10;有限队列ArrayBlockingQueue
		ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
				new ArrayBlockingQueue<>(5));

		for (int i = 0; i < 15; i++) {
			Task task = new Task(i);
			executor.execute(task);
			System.out.println("线程数目:" + executor.getPoolSize() + "队列大小:" + executor.getQueue().size());
		}
		executor.shutdown();
	}

	static class Task implements Runnable {
		private int taskNum;

		public Task(int taskNum) {
			this.taskNum = taskNum;
		}

		@Override
		public void run() {
			System.out.println("第-" + taskNum + "-个task正在运行");
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("第-" + taskNum + "-个task停止运行");
		}

		public int getTaskNum() {
			return taskNum;
		}

		public void setTaskNum(int taskNum) {
			this.taskNum = taskNum;
		}
	}
}

在用构造器构建好线程池之后,还是可以对线程池参数进行调整,如以下这些方法:

Executors构建线程池

首先需要指出的一点是,用Executors构建的线程池,其参数是不可再调整的,因为Executors提供的线程池构建方法,返回的都是ExecutorService的实例,ExecutorService中并没有修改线程池的方法。

-CachedThreadPool:

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

按需构建线程的线程池,maximumPoolSize为无限大,时间单位为秒,移除空闲时间超过60秒的线程,使用的work queue为SynchronousQueue。

CachedThreadPool适用于处理生命周期短(short-lived)、异步的任务。而且能够保证线程的重用(reuse):如果线程池存在空闲的线程,将会利用这些线程而不是新建线程。

-FixedThreadPool:

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

固定大小的线程池,corePoolSize和maximumPoolSize相同,可以指定线程数nThreads,keep alive times为0,即创建的线程会一直存在,使用无线队列LinkedBlockingQueue。FixedThreadPool中最多只能存在nThreads个线程,多余的任务会在队列中存储。

-ScheduledThreadPool:

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

 

newScheduledThreadPool()返回的是ScheduledExecutorService,ScheduledExecutorService继承自ExecutorService,额外提供了schedule(Runnable command,long delay, TimeUnit unit)等方法,能够定时执行任务

-SingleThreadExecutor:

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

只有一个线程,任务队列为无限大。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值