欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程池

程序员文章站 2022-05-06 07:54:27
...

线程池

       线程池是一种线程容器,在线程池中,有一些活跃线程,当需要线程的时候可以从线程池中获取一个线程,当完成工作后,可以将线程放回到线程池中,方便其他人使用。线程池避免了每次需要线程的时候都要创建线程和销毁线程,节省了时间和空间开销,提升了性能。

       JDK内置支持线程池,在concurrent并发包里包含了线程池的相关类。Executors类是线程池工厂,通过Executors可以获得特定功能的线程池。

       Executors的主要工厂方法如下:

public static ExecutorService newFixedThreadPool (int nThread);

public static ExecutorService newSingleThreadExecutor ();

public static ExecutorService newCachedThreadPool ();

public static ScheduledExecutorService newSingleThreadSchuduledExecutor ();

public static ScheduledExecutorService newScheduledThreadPool (int corePoolSize);

       这些方法会返回特定功能的线程池:

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

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

       •newCachedThreadPool()方法,返回一个可根据实际情况调整线程数量的线程池。线程池中线程的数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程都在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务完成之后,进入线程池进行复用。

      •newSingleThreadSchuduledExecutor()方法,返回一个ScheduledExecutorService对象,线程池大小为1ScheduledExecutorService在ExecutorService接口基础上扩展了功能,可以在给定时间执行某个任务,如在固定的时间后执行或者周期性的执行某个任务。

       •newScheduledThreadPool(int corePoolSize)方法,也返回一个ScheduledExecutorService对象,但是线程池可以指定线程数量。

应用示例:

public class ThreadPoolDemo implements Runnable{
	public void run(){
		System.out.println("I am "+Thread.currentThread().getName());
	}

	public static void main(String[] args) {
		ThreadPoolDemo demo=new ThreadPoolDemo ();
		ExecutorService exe=Executors.newFixedThreadPool (3);
//		ExecutorService exe=Executors.newSingleThreadExecutor ();
//		ExecutorService exe=Executors.newCachedThreadPool ();
		for(int i=0; i<6; i++){
			exe.execute (demo);
//			exe.submit (demo);//与上一句有些许差别
		}
	}
}

1、核心线程池的内部实现

       对于几个核心的线程池,newFixedThreadPool()、newSingleThreadExecutor()和newCachedThreadPool(),它们的内部实现都使用了ThreadPoolExecutor。

       三个线程池的实现方式:

线程池

线程池

线程池

线程池

从上述代码可以看出,这三个核心线程池都是ThreadPoolExecutor类的封装。下面看一下ThreadPoolExecutor的构造函数:

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

构造函数的各个参数含义如下:

       corePoolSize:指定了线程池中线程的数量。

       maximumPoolSize:指定了线程池中线程的最大数量。

       keepAliveTime:当线程池中线程数量超过corePoolSize时,多余的空闲线程的存活时间。即,超过corePoolSize的线程,在多长时间后会被销毁。

       unit:keepAliveTime的时间单位。

       workQueue:任务队列,存放被提交但尚未被执行的任务。

       threadFactory:线程工厂,用于创建线程,一般使用默认。

       handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。

       workQueue参数:

       其是一个BlockingQueue接口的对象,仅用于存放Runnable对象。ThreadPoolExecutor的构造函数中可以使用如下几种BlockingQueue:

       •直接提交的队列:SynchronousQueue。SynchronousQueue没有容量,每一个插入操作都要等待一个相应的删除操作,每一个删除操作都要等待对应的插入操作。使用Synchronous,并不会真正保存任务,其总是将新任务提交给线程执行,如果没有空闲的线程,则新建一个线程,如果线程数量已经达到最大值,则执行拒绝策略。

       •有界任务队列:ArrayBlockingQueue,需要指定该队列的最大容量。若使用有界的任务队列,当有新的任务需要执行,如果线程池的实际线程个数小于corePoolSize,则会优先创建线程,若大于corePoolSize,则会将新任务添加到任务等待队列。若队列已满,无法加入,若总线程数小于maximumPoolSize,则创建新线程执行任务。若大于maximumPoolSize,则执行拒绝策略。

       •*任务队列:LinkedBlockingQueue。与有界任务队列相比,除非系统资源耗尽,否则*任务队列不存在入队失败。若使用*任务队列,当有新的任务到来,如果线程数量小于corePoolSize,则创建新的线程执行任务,当线程数达到corePoolSize时,就不会再增加。若后续还有新任务,直接加入到队列中。

      •优先任务队列:PriorityBlockingQueue,其是带有执行优先级的队列,是一个特殊的*队列。ArrayBlockingQueue和LinkedBlockingQueue都是按照先进先出处理任务的。而PriorityBlockingQueue是根据任务自身的优先级选择任务执行。

       ThreadPoolExecutor调度策略:

线程池

handler:拒绝策略

       拒绝策略是系统超负荷运行时的补救措施,通常是由于压力太大而引起的,也就是线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列中也已经排满了,再也塞不下新任务。这时就需要拒绝策略处理这个问题。

       JDK内置四种拒绝策略:

       AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。

       CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中运行任务,不会真正丢弃任务,但会导致提交任务的线程性能急剧下降。

       DiscardOldestPolicy策略:丢弃一个最老的请求,并尝试再次提交该任务。

       •DiscardPolicy策略:默默丢弃无法处理的任务,不作任何处理。

 

参考:《实战Java高并发程序设计》