JAVA的并发编程(七):Java的四种线程池和自定义线程池

空扰寡人 提交于 2019-12-02 19:57:08

目录

一、Executors线程框架

1)概述

2)Java的四种线程池

3)自定义线程池

1.使用有界队列的自定义线程池

2.使用无界队列的自定义线程池


一、Executors线程框架

1)概述

传统方式,通过new Thread来创建对象,具有以下几个特点:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。

相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
 

2)Java的四种线程池

Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

若是Executors提供的线程池还不能满足你的使用要求,你可以使用自定义的线程池。

Executors内的线程池其实是公用了一个方法来创建的,可以通过输入具体的参数来生成指定功能的线程池

同样,还可以指定或者自定义具体的线程拒绝策略RejectedExecutionHandler 

jdk提供的拒绝策略分别为有:

AbortPolicy直接抛出RejectedExecutionException拒绝异常,程序继续执行
CallerRunsPolicy只要线程池未关闭,直接在调用线程运行当前被丢弃的任务
DiscardOldestPolicy丢弃最老或者说最早插入队列的那个请求,尝试再次提交当前任务
DiscardPolicy丢弃任务,不给予任何处理

通常来说,可以自定义拒绝策略,或者在插入任务之前就打印log或者发放httpClient通知来保证数据的安全。


public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                              int maximumPoolSize,//最大线程池大小
                              long keepAliveTime,//活跃时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//存放线程的队列
                              ThreadFactory threadFactory,//线程工厂
                              RejectedExecutionHandler handler//线程拒绝方式
                          ){...}
                                                        

3)自定义线程池

1.使用有界队列的自定义线程池

在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
若大于corePoolSize,则会将任务加入队列,
若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,
若线程数大于maximumPoolSize,则执行拒绝策略,或其他自定义方式。

public class UseThreadPoolExecutor1 {
	public static void main(String[] args) {
		ThreadPoolExecutor pool = new ThreadPoolExecutor(
				1, 				//coreSize
				2, 				//MaxSize
				60, 			//60
				TimeUnit.SECONDS, 
				new ArrayBlockingQueue<Runnable>(3)	//指定一种队列 (有界队列)
				);	
		MyTask mt1 = new MyTask(1, "任务1");
		MyTask mt2 = new MyTask(2, "任务2");
		MyTask mt3 = new MyTask(3, "任务3");
		MyTask mt4 = new MyTask(4, "任务4");
		MyTask mt5 = new MyTask(5, "任务5");
		MyTask mt6 = new MyTask(6, "任务6");
		
		pool.execute(mt1);
		pool.execute(mt2);
		pool.execute(mt3);
		pool.execute(mt4);
		pool.execute(mt5);
		pool.execute(mt6);

		pool.shutdown();
		
	}
}

插入了6个任务,创建线程执行任务1,234插入队列,然后创建线程执行任务5,然后2个线程依次执行队列中的任务。

默认采用AbortPolicy拒绝策略,程序抛出异常后继续执行

执行结果:

2.使用无界队列的自定义线程池

在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
若大于corePoolSize,则会将任务加入队列。
如果任务创建和处理的速度差异很大,无界队列会保持快速增长,直到出现oom内存溢出。

public class UseThreadPoolExecutor2 implements Runnable{
	private static AtomicInteger count = new AtomicInteger(0);
	@Override
	public void run() {
		try {
			int temp = count.incrementAndGet();
			System.out.println("任务" + temp);
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) throws Exception{
		BlockingQueue<Runnable> queue = 
				new LinkedBlockingQueue<Runnable>();
		ExecutorService executor  = new ThreadPoolExecutor(
					2, 		//core
					10, 	//max
					120L, 	//2fenzhong
					TimeUnit.SECONDS,
					queue);
		for(int i = 0 ; i < 20; i++){
			executor.execute(new UseThreadPoolExecutor2());
		}
		Thread.sleep(1000);
		System.out.println("queue size:" + queue.size());		//10
		Thread.sleep(2000);
	}
}

插入20个任务,由于corePoolSize核心线程数为2,所以执行2个任务,其他18个任务存入队列依次执行

结果如下:

 

PS:博文仅作为个人学习笔记,如有错误欢迎指正,转载请注明出处~

详见:笔记分类导航目录

参考资料:

视频资料若干。。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!