线程池的概念
线程池,一种多线程的处理形式,处理过程中可以将任务添加到队列中,然后创建线程启动这些任务。 这里的任务就是实现了Runnable或Callable接口或继承Thread类的对象实例(通俗讲就是需要执行的代码逻辑)。
线程池属于JUC( java.util.concurrent )包。
使用线程池好处:
- 重复利用已有的线程继续执行任务,避免线程在创建和销毁时造成的消耗。
- 由于没有创建和销毁时造成的消耗,因此可以提高系统响应速度。
- 可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等。
例子: 线程池中已有10个已经就绪的线程,此时有100个任务等待执行。
那么系统就会先分配10个已就绪的线程去处理其中的10个任务,剩下90个任务暂时等待。待正在运行的线程空闲后,立马又去执行其他的任务。这样就避免了线程创建和销毁时造成的消耗。因为线程池中的线程重复利用了,自然不需要再创建线程了。
线程池的分类和作用:
1、newCachedThreadPool (可缓存)
作用:创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。
特征:
(1)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
(2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
(3)当线程池中,没有可用线程,会重新创建一个线程
创建方式: Executors.newCachedThreadPool();
2、newFixedThreadPool (定长)
作用:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
特征:
(1)线程池中的线程处于一定的量,可以很好的控制线程的并发量
(2)线程可以重复被使用,在显示关闭之前,都将一直存在
(3)超出一定量的线程被提交时候需在队列中等待
创建方式:
(1)Executors.newFixedThreadPool(int nThreads);//nThreads为线程的数量
(2)Executors.newFixedThreadPool(int nThreads,ThreadFactory threadFactory);//nThreads为线程的数量,threadFactory创建线程的工厂方式
3、newSingleThreadExecutor (单例)
作用:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
创建方式:
(1)Executors.newSingleThreadExecutor() ;
(2)Executors.newSingleThreadExecutor(ThreadFactory threadFactory);// threadFactory创建线程的工厂方式
4、newScheduleThreadPool (定时)
作用: 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
特征:
(1)线程池中具有指定数量的线程,即便是空线程也将保留
(2)可定时或者延迟执行线程活动
创建方式:
(1)Executors.newScheduledThreadPool(int corePoolSize);// corePoolSize线程的个数
(2)newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);// corePoolSize线程的个数,threadFactory创建线程的工厂
5、newSingleThreadScheduledExecutor
作用: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
(2)可定时或者延迟执行线程活动
创建方式:
(1)Executors.newSingleThreadScheduledExecutor() ;
(2)Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory) ;//threadFactory创建线程的工厂
并发队列
非阻塞队列:
当队列中数据数量已达到队列容器大小时,再执行入队操作将造成数据丢失。
当队列中没有数据时,执行出对操作,取出的数据为null。
阻塞队列
当队列中数据数量已达到队列容器大小时,再执行入队操作,数据将在容器外等待,等到队列有数据出对时,再插入数据。
当队列中没有数据时,执行出对操作,将等待,直到有数据入队。
线程池中使用的正是阻塞队列。
ThreadPoolExecutor
corePoolSize: 核心线程数,线程池的基本大小。
(1)核心线程会一直存在,即使没有任务执行;
(2)当线程数小于核心线程数的时候,即使有空闲线程,也会一直创建线程直到达到核心线程数;
(3)设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。
maximumPoolSize:最大线程数,线程池中能够容纳的最大线程数量
如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务
keepAliveTime: 线程空闲时间
(1)当线程空闲时间达到keepAliveTime时,线程会退出(关闭),直到线程数等于核心线程数;
(2)如果设置了allowCoreThreadTimeout=true,则线程会退出直到线程数等于零。
unit: 线程空闲时间单位
BlockingQueue workQueue: 阻塞队列
其中还有
allowCoreThreadTimeout: 允许核心线程超时
rejectedExecutionHandler: 任务拒绝处理器
threadFactory: 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程做些更有意义的事情,比如设置daemon和优先级等等
(1)当线程数量达到最大线程数,且任务队列已满时,会拒绝任务;
(2)调用线程池shutdown()方法后,会等待执行完线程池的任务之后,再shutdown()。如果在调用了shutdown()方法和线程池真正shutdown()之间提交任务,会拒绝新任务。
线程池工作流程:
搞懂线程池工作流程
首先创建一个线程任务
class TestThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
main函数中创建线程池
//创建线程池
ThreadPoolExecutor pool =
new ThreadPoolExecutor(1,2,3, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3));
核心线程数为1,最大线程数为2,线程空闲时间为3,单位为秒,阻塞队列容量为3
1.执行第一个任务:
pool.execute(new TestThread());
结果为:
任务被线程1执行,此时的任务是由核心线程在处理。
2.执行第二、三、四个任务
//执行第一个任务
pool.execute(new TestThread());
//执行第二、三、四个任务
pool.execute(new TestThread());
pool.execute(new TestThread());
pool.execute(new TestThread());
结果:
核心线程执行一个任务,剩下的任务进入阻塞队列中等待处理,因此全部都由核心线程执行任务。
3.执行第五个任务
//执行第一个任务
pool.execute(new TestThread());
//执行2,3,4,任务
pool.execute(new TestThread());
pool.execute(new TestThread());
pool.execute(new TestThread());
//执行第五个任务
pool.execute(new TestThread());
结果:
或者
执行第五个任务时,阻塞队列已满,因此再创建一个线程去执行第五个任务。由于核心线程和创建的新线程谁先空闲不确定,因此剩余的任务将由核心线程和创建的新线程共同分摊处理。就会出现如上图的随机输出,多次执行程序效果更明显。
4.执行第六个任务
//执行第一个任务
pool.execute(new TestThread());
//执行2,3,4,任务
pool.execute(new TestThread());
pool.execute(new TestThread());
pool.execute(new TestThread());
//执行第五个任务
pool.execute(new TestThread());
//执行第六个任务(报错)
pool.execute(new TestThread());
结果:
出现错误信息。原因在于,当执行第五个任务时新创建了一个线程,线程池中有2个线程,达到了最大线程数。此时阻塞队列还是满的,再加入一个任务,需要再创建一个新线程去处理,因此就超过了最大线程数,就报错啦。
拒绝策略
AbortPolicy
默认饱和策略。当线程池饱和时使用该策略,会抛出RejectExecutionException异常。
DiscardPolicy
丢弃无法加载的任务,啥也不做。
DiscardOldestPolicy
将阻塞队列中的头元素出队抛弃,再尝试提交任务。如果此时阻塞队列使用
PriorityBlockingQueue 优先级队列,将会导致优先级最高的任务被抛弃,因此不建议将该种策略配合优先级队列使用。
CallerRunsPolicy
既不抛弃任务也不抛出异常,直接运行任务的run方法,换言之将任务回退给调用者来直接运行。使用该策略时,线程池饱和后将由调用线程的主线程自己来执行任务,因此在执行任务的这段时间里,主线程无法再提交新任务,从而使线程池中工作线程有时间将正在处理的任务处理完成。