线程池解析

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
  如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
  这就出现了线程池。

线程池

线程池:
当并发线程多了之后,每个线程的频繁创建于销毁会导致性能降低,不好管理,为了解决这样的问题,就出现了线程池,线程池就是管理线程,我们不需要关心的创建与销毁,大大增加了我们开发的效率和程序的性能。

作用:
	1、	降低资源的消耗。降低线程创建和销毁的资源消耗。
	2、	提高响应速度:线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间。
	3、	提高线程的可管理性。

线程池的创建

ThreadPoolExecutor,jdk所有线程池实现的父类。

	其中各个参数含义:
	int corePoolSize:线程池中核心线程数小于corePoolSize(线程池大小),就会创建新线程去执行任务。
					  等于corePoolSize,这个任务就会保存到BlockingQueue。
					  如果调用prestartAllCoreThreads方法就会一次性的启动corePoolSize个数的线程。
	
	int maximumPoolSize:允许的最大线程数,当BlockingQueue也满了,并且小于maximumPoolSize时候就会再次创建新的线程。
	
	keepAliveTime:线程空闲下来后存活的时间,这个参数只在大于corePoolSize才有用。
	
	TimeUnit unit:存活时间的单位值。
	
	BlockingQueue<Runnable> workQueue:保存任务的阻塞队列。
	
	ThreadFactory threadFactory;创建线程的工厂,给新建的线程赋予名字.

饱和策略

	RejectedExecutionHandler handler(饱和策略,四种):
		1,AbortPolicy :直接抛出异常,默认。
		2,CallerRunsPolicy:用调用者所在的线程来执行任务,不用线程池执行了。
		3,DiscardOldestPolicy:丢弃阻塞队列里最老的任务,队列里最靠前的任务。
		4,DiscardPolicy :当前任务直接丢弃。
		饱和策略:即是当线程数大于maximumPoolSize的时候,已经饱和的了,提供一个这四种处理的策略。
		实现自己的饱和策略,需要实现RejectedExecutionHandler接口。

工作机制

1)线程池判断核心线程池里的线程是否全都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2)线程池判断任务阻塞队列是否已经满。如果任务阻塞队列没有满,则将新提交的任务存储在这个任务阻塞队列里。如果任务阻塞队列满了,则进入下个流程。
3)线程池判断线程池中的工作的线程数是否小于最大线程池数。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

方法:
	提交任务:
		execute(Runnable command) :不需要返回
		Future<T> submit(Callable<T> task) :需要返回
	关闭线程池:
		shutdown(),shutdownNow();
		shutdownNow():设置线程池的状态,还会尝试停止正在执行和没有执行任务的线程。
		shutdown():设置线程池的状态,只会中断所有没有执行任务的线程。

合理配置线程池

	根据任务的性质区分:
		计算密集型(CPU密集型),IO密集型,混合型
		计算密集型:加密,大数分解,正则…….,这种类型,在使用线程池的时候,设置线程数适当小一点,最大推荐:机器的Cpu核			  心数+1。(注意:这是逻辑核心数)
					1,为什么+1,?
						为了防止页缺失(页缺失:数据未全部从磁盘读取到内存中,数据丢失了),(机器的Cpu核心=Runtime.getRuntime().availableProcessors();)
						
					2,为什么不是大于CPU核心数+1?
						因为如果线程数大于太多CPU核心数,就会出现不必要的上下文切换,造成性能消耗。
						
		IO密集型:读取文件,数据库连接,网络通讯, 线程数适当大一点,机器的Cpu核心数*2,
		混合型:尽量拆分,IO密集型>>计算密集型,拆分意义不大,IO密集型~计算密集型
		
	注意:队列的选择上,应该尽量使用有界,无界队列可能会导致内存溢出,发生OOM。

Executor框架

1,一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor,是线程池的定级接口,ThreadPoolExecut or是它的实现类。

2,ExecutorService:是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务 执行状况返回Future的方法。

3,可以通过Executors这个工厂类获取到一些其他的预定义线程池,这些线程池都是Executor的实现类。

预定义的线程池

	FixedThreadPool:
		创建固定线程数量的,适用于负载较重的服务器,其中使用了无界的阻塞队列(LinkedBliockingQueue)。
		
	SingleThreadExecutor:
		创建单个线程,可以让任务按顺序执行,不会有多个线程活动,使用了无界队列。
		
	CachedThreadPool:
		会根据需要来创建新线程的,执行很多短期异步任务的程序,使用了SynchronousQueue。
		
	WorkStealingPool(JDK7以后):
		基于ForkJoinPool实现,是一个工作密取的线程池。
		
	ScheduledThreadPoolExecutor:
		需要定期执行周期任务,Timer不建议使用了。
		
	newSingleThreadScheduledExecutor:
		只包含一个线程,只需要单个线程执行周期任务,保证顺序的执行各个任务。
		
	newScheduledThreadPool:
		可以包含多个线程的,线程执行周期任务,适度控制后台线程数量的时候。
		
		方法说明:
		schedule:只执行一次,任务还可以延时执行
		
		scheduleAtFixedRate:提交固定时间间隔的任务,间隔是上一次开始的时刻到下一次开始的时刻的间隔。
		
			scheduleAtFixedRate任务超时:
				假设:规定60s执行一次,第一个任务 时长 80s,第二个任务20s,第三个任务50s,则:
					第一个任务第0秒开始,第80S结束;
					第二个任务第80s开始,在第100秒结束;
					第三个任务第120s秒开始,170秒结束;
					第四个任务从180s开始;
					
		scheduleWithFixedDelay:提交固定延时间隔执行的任务,间隔是上一次完成的时刻到下一次开始执行的时刻的间隔。
		
		注意:当任务执行异常,如果直接抛出,会阻塞住当前任务,其他任务不会影响,所以,尽量用try,catch处理整个任务代码块,防止任务异常的时候阻塞。

CompletionService

问题:当线程池中的任务是有返回值的时候,放入线程池的任务是有顺序的,所以当这些任务执行完成之后,从阻塞队列中拿执行的任务的结果的时候,也只能是按照放入的顺序,先进先出的规则拿去结果,这样可能会造成CPU的浪费,比如我只想拿第三个任务执行的结果,这时候我必须得等到前两个任务执行完才能拿到,同时,也不能让先完成的任务先去执行下一个任务,所以这样由此产生了CompletionService来解决。

	CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值