线程模型分为:内核线程、用户线程。
操作系统中内存空间也是分为:内核空间、用户空间。
对于操作系统来说,操作系统只能识别内核线程,而对于用户线程,操作系统是无感知的,也就是说不识别的。
用户线程想得到cpu的使用权,必须要通过内核线程去间接操作。 为什么要这样设计呢?是为了提高安全性,就像是进程隔离一样。
内核线程才具有cpu的强夺权,用户线程不具备抢夺使用权的资源。
多核cpu可以实现真正的并行,一个内核线程获取一个核心的cpu的使用率,其他内核线程可以获取其他核心的使用率,从而实现真正并行。
jvm使用的就是内核线程模型,也就是说当我们手动创建一个线程后,任务管理器中的内核线程数量一会一直加。
jvm中的线程模型和内核线程是1对1的关系,也就是说在java冲创建一个线程,就会有一个内核线程在操作系统中被创建。
对于操作系统来说,线程执行并不是说一个线程一直在执行,而是这个线程运行一下,那个线程运行一下,采用一种中断机制;也正是因为这种中断机制,而每次线程中断,都要保护好其他线程运行的线程环境,然后进行线程的上下文切换。
1、线程的创建和销毁对于操作系统来说是重量级操作。
2、线程频繁的上下文切换,需要大量的资源。
3、可以提高线程的重用性,能够更好的管理线程。
等等
jdk中提供了五种线程池。
在jdk中提供了一个工具类Executors可以来创建线程池。
以下是Executors类的结构。
newFixedThreadPool:固定线程数的线程池。
newWorkStealingPool:可以偷其他线程队列中的任务,线程池中维护了多个任务队列。
newSingleThreadExecutor:一个线程的线程池。
newCachedThreadPool:无界限创建线程的线程池。
newScheduledThreadPool:可以定时的线程线程池。
答案是:哪个都不选。
为什么哪个都不选呢?让我们看下一下这些提供出来的线程池是如何创建的。
例如:newFixedThreadPool线程池,它的内部创建了一个LinikedBlockingQueue的队列。
这个队列就是用来存储任务的队列。
继续点进去,发现它是创建了一个Integer.MAX_VALUE大小的队列,这里的大小就是2^31-1
试想一下,如果有大量的请求一直发送到后台,这个队列就可以无限的接受队列。就很容易出现OOM异常。
可以查看一下其他几种线程池,发现除了newWorkStealingPool,其他四个都是这样子的。
从上图我们可以看到,其实Executors提供创建的线程池的方法都是在创建ThreadPoolExecutor对象。我们就是要来使用ThreadPoolExecutor自己创建线程池。
首先,让我们来看下ThreadPoolExecutor有哪些参数:
1、int corePoolSize:
核心工作线程数,线程池启动创建的线程数。
2、int maximumPoolSize:
最大线程数,平时只有核心数个线程在执行,如果任务特别多,任务队列满了之后,就会出创建临时线程。
能够创建多少个临时线程呢?临时线程数=最大线程数-核心工作线程数
3、long keepAliveTime:
临时线程空闲最大的存活时间。
4、TimeUnit unit:
存活时间单位。
5、BlockingQueue workQueue:
当任务数超过核心工作线程数之后用于存储任务的队列。。
任务队列也有分:
有界队列:
只能存储指定数量个任务进队列中。
无界队列:
可以无限将任务存储进队列中,只要硬件条件允许。
阻塞队列:
同一个时刻只能一个一个任务入队列,是有序的,是线程安全的。
6、ThreadFactory threadFactory:
线程创建工厂,一般我们使用默认的Executors.defaultThreadFactory()。
7、RejectedExecutionHandler handler:
拒绝策略,当任务数超过最大线程数之后,并且任务队列也存储满了,如果还有任务到来,任务就无法处理。拒绝策略就是为了解决这种问题而产生的。
默认提供了四种拒绝策略。默认使用的是AbortPolicy,也可以自己自定义拒绝策略。例如:如果任务队列满了,可以记录到日志中,然后在进行重新入队操作。
jdk提供的四种策略:
AbortPolicy:
丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy:
如果任务队列满了,直接丢弃任务,也不抛出异常。
CallerRunsPolicy:
由提交任务到线程池的那个线程来处理该任务。
DiscardOldestPolicy:
丢弃任务队列中最前面的任务,并且将当前提交的任务存入任务队列中。
创建拒绝策略对象:
例如:
ThreadPoolExecutor.DiscardPolicy discardPolicy = new ThreadPoolExecutor.DiscardPolicy();
也可以自定义拒绝策略,创建一个类实现RejectedExecutionHandler接口。
线程池启动,初始化线程池。线程池通过线程工厂创建核心线程。
当任务过来之后,先将任务交给核心线程执行;如果核心线程还有,则当任务来了之后,直接创建核心线程并且将任务交给核心线程处理。
如果没有足够的核心线程,就将任务存入任务队列中,如果任务队列满了,则创建临时线程,然后将任务交给临时线程处理。如果临时线程数+核心线程数创建的数量等于最大线程数;并且任务队列也满了,则会执行拒绝策略。
1、corePoolSize设置为0,只设置最大线程数,这样所有的线程都是临时线程,只要任务处理完了之后,并且线程超过了keepAliveTime时间,就会被销毁了。
2、jdk1.6起,支持allowCoreThreadTimeOut,表示核心线程也和临时线程一样接受keepAliveTime的管理。
所以,只要线程池调用allowCoreThreadTimeOut(true) 方法,设置allowCoreThreadTimeOut的值为true。
3、添加完任务之后,使用shutdown()方方法,当所有的任务执行完之后,就会自动关闭线程池了,需要注意的是,调用了shutdown()方法后,线程池不能接收新的任务了。
注意:
1、任务队列满了才会创建非核心线程数去执行任务,并且如果非核心线程空闲了一段时间 (保活时间keepAliveTime) 之后,这些线程会被销毁。