线程池
文章目录
一,java线程的理解
一,线程模型分类:
1.用户级别线程(User-level Thread ULT)
用户程序实现,不依赖操作系统核心,应用提供创建,同步,调度和管理线程的函数来控制用户线程,不需要用户态/内核态切换,速度块内核对ULT无感知,线程阻塞则进程(包括它的所有线程)阻塞
----用户级线程是由进程创建的线程,由进程来调度,按照顺序一个一个的进行,一个线程阻塞,那么它的队列就执行不下去,就会阻塞进程
2.内核级线程(Kernel-Level Thread KLT)
系统内核管理线程,内核保存线程的状态和上下文信息,线程阻塞不会引起进程阻塞,在多处理器气系统上,多线程在多处理器上并行运行。线程的创建,调度和管理由内核完成,效率比ULT要慢,比进快程操作快。
用户级别线程和内核级线程的关系
内核级线程会存储在内核空间能够直接操作cpu,用户级别线程存在于用户空间,需要通过提供交互的接口而通过内核空间进行操作cpu,这是一种安全设置机制
例:
str="my string" //存在于用户空间
x=x+2
file.write(str)//切换到内核空间
y+x+4//切换回用户空间
Java线程与系统内核线程
Java线程创建是依赖于系统内核,通过JVM调用系统库创建内核线程,内核线程与Java-Thread是1:1的映射关系
二,使用线程池的优势:
线程调度cpu运行,操作系统是按照时间片的轮询,当一个线程的时间片用完,但是任务没有处理完,就会保存相应的状态信息,这个保存在内核空间的Tss任务段这里,等重新一轮的时间片过来,这个线程就要重新加载保存在内核空间中的这些信息,会涉及到用户空间和内核空间的转换,当处理高并发问题时,这种转换还有线程的创建和销毁都是重量级的操作,所以会拖慢效率,使用线程池就可以解决这些问题
如图所示:
线程池的意义:
线程是稀缺资源,它的创建与销毁是比较重且耗资源的操作,而java线程依赖于内核线程,创建线程需要进行操作系统状态的切换,为避免资源过度消耗需要设法重用线程执行多个任务,线程池就是一个线程缓存,负责对线程统一分配,调优与监控
线程池的优势:
1.重用存在的线程,减少线程创建,消亡的开销,提高性能
2.提高响应速度,当任务到达时,任务可以不需要的等到线程创建就能立即执行
3.提高线程的客观理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程可以进行统一的分配,调优和监控。
三,线程池的五种状态:
Running:能接收新任务,以及处理已经添加的任务
Shutdown:不接收新任务,可以处理已经添加的任务
Stop:不接收新任务,不处理已经添加的任务,并且中断正在处理的任务
Tidying:所有任务已经终止,ctl记录的任务数量为0(tcl负责记录线程池的运行状态与活动线程数)
Terminated:线程池彻底终止,则线程池转化为terminated状态
状态转换图:
二,线程池的的源码:
一,executor框架
executor:是一个接口,里面只有一个执行的方法
executorService:一个子接口,里面定义了线程池的各个状态
一,线程池的创建方式:
executors:线程池的一个工具类,工具类有五种创建线程的方法:
1.newScheduledThreadPool()
实现定时和周期性任务的线程池,corePoolSize是传进来的固定值,maximumPoolSize无限大,当执行scheduleAtFixedRate或者scheduleWithFixedDelay方法时,会向DelayedWorkQueue添加一个实现RunnableScheduledFuture接口的ScheduledFutureTask(任务的包装类),并会检查运行的线程是否达到corePoolSize。当执行完任务后,会将ScheduledFutureTask中的time变量改为下次要执行的时间并放回到DelayedWorkQueue中。
2.newCachedThreadPool()
根据需要创建线程的线程池,orePoolSize是0,maximumPoolSize是Int的最大值,队列是阻塞队列SynchronousQueue,这个队列没有缓冲区,所以其中最多只能存在一个元素,有新的任务则阻塞等待
3.newFixedThreadPool()
可重用固定线程数的线程池,超出的线程会在队列中等待,只有固定数量的核心线程,不存在非核心线程,任务队列采用的是LinkedBlockingQueue。
4.newSingleThreadPool()
使用单个线程工作的线程池,该线程池才用链表阻塞队列LinkedBlockingQueue,先进先出原则,所以保证了任务的按顺序逐一进行
5.newWorkStealingPool()
例:
ExecutorService pool=Executors.newFixedThreadPool(5)//一池五个处理线程
ExecutorService pool=Executors.newSingleThreadPool()//一池一个处理线程
ExecutorService pool=Executors.newCachedThreadPool()//一池N个处理线程
ExecutorService pool=Executors.newScheduledThreadPool(int n)//时间表
ExecutorService pool=Executors.newWorkStealingPool()//jdk1.8出现的新特性,可以窃取任务,缓解线程的饥饿
executors的五种创建方式,里面的队列的最大值(Integer.Max_Values),任务会一直堆积,造成OOM的情况,所以,不推荐使用executors的创建方式
创建线程池的类ThreadPoolExecutor的类型图
必须手动的创建线程池:
final ExecutorService pool=new ThreadPoolExecutor(2,3,60,TimeUnit.SECONDS,new ArrayBlockingQueue<Runable>(5)),Executor.defaultThreadFactory());
try{
for(int i=0;i<9;i++){
pool.execute(new Task(i));//Task实现了Runnable/Collable接口,在这个类里面执行run方法执行真正的任务
}
}
catch(..){
..
}
finally{
pool.shutdown();
}
二,参数介绍:
int corePoolSize:核心线程数
在创建完线程池之后,核心线程先不创建,在接到任务之后创建核心线程。并且会一直存在于线程池中,数量一般情况下设置为CPU核数的二倍即可。
int maximumPoolSize:最大线程数
核心线程都被占用,但还有任务要做,就创建非核心
long keepAliveTime: 非临时线程存活的时间
TimeUnit unit: 时间的单位(一个枚举类型)
BlockingQueue< Runnable> workQueue: 任务队列
任务进来之后先分配给核心线程执行,核心线程如果都被占用,并不会立刻开启非核心线程执行任务,而是将任务插入任务队列等待执行,核心线程会从任务队列取任务来执行,任务队列可以设置最大值,一旦插入的任务足够多,达到最大值,才会创建非核心线程执行任务。
有四种:
(1)SynchronousQueue:这个队列接收到任务会直接提交给线程处理,而不保留它,如果线程都在工作就新建一个线程来处理这个任务,所以使用这个类型队列的时候:maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
(2)LinkedBlockingQueue:队列没有最大值限定,所以接收到任务时,新建线程处理,如果核心线程数等于核心线程数上限,则进入队列等待
(3)ArrayBlockingQueue:限定队列的长度,如果总线程数到maxmumPoolSize,并且队列也满了,会发生错误,或执行定义好的饱和策略
(4)DelayedWorkQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
ThreadFactory threadFactory:线程工厂
用线程工厂给每个创建出来的线程设置名字,一般无需设置
RejectedExecutionHandler handler:拒绝策略(
jdk提供了四种),可以自定义,只要实现这个接口
(1)AbortPolicy(){丢弃一个任务//会抛出一个异常} 默认
(2)DiscardPolicy(){直接丢弃一个任务}
(3)DiscardOldestPolicy(){会丢弃队列最前面的一个任务,重新尝试调用这个任务。一直重复}
(4)CallerRunPolicy(){当线程爆满时,将任务返回给任务的调用者由它去执行,即用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度}
三,线程的执行流程:
关键源码: