面向对象基础篇 – 多线程编程 – 线程池
- Java线程池是1.5 提供juc包中的,底层实现其实就是Callable 和 Future 接口
- 根据情况,我们可以创建很多种不同场景的线程池
Executors.newSingleThreadExecutor()
- 单例线程池
- 只有一个线程,固定不会变化
@Test
void test01(){
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}
/*
执行结果
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
*/
Executors.newFixedThreadPool(5)
- 固定大小的线程池
- 使用场景:并发量不会发生变化(并发量的变化很小)
@Test
void test02(){
//开辟5个线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int index = i ;
executorService.submit(()-> System.out.println(Thread.currentThread().getName()+"-->"+index));
}
}
/*
执行输出
pool-1-thread-1-->0
pool-1-thread-2-->1
pool-1-thread-5-->4
pool-1-thread-4-->3
pool-1-thread-4-->6
pool-1-thread-4-->7
pool-1-thread-4-->8
pool-1-thread-3-->2
pool-1-thread-4-->9
pool-1-thread-1-->5
*/
Executors.newCachedThreadPool()
- 可缓存的线程池
- 若线程超过处理所需,缓存一段时间后会回收
- 若线程不够,则新建线程
- 并发量变化比较明显的可以用这个
@Test
void test03(){
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
final int index = i ;
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"-->"+index);
});
}
}
/*
执行输出
pool-1-thread-3-->2
pool-1-thread-20-->19
pool-1-thread-14-->13
pool-1-thread-18-->17
pool-1-thread-7-->6
pool-1-thread-12-->11
pool-1-thread-16-->15
pool-1-thread-17-->16
pool-1-thread-15-->14
pool-1-thread-1-->0
pool-1-thread-2-->1
pool-1-thread-13-->12
pool-1-thread-9-->8
pool-1-thread-4-->3
pool-1-thread-6-->5
pool-1-thread-19-->18
pool-1-thread-5-->4
pool-1-thread-8-->7
pool-1-thread-11-->10
pool-1-thread-10-->9
*/
Executors.newScheduledThreadPool()
- 让线程延时执行
@Test
void test04(){
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
for (int i = 0; i < 10; i++) {
final int index = i ;
//参数一,要执行的任务
//参数二,延时多久
//参数三,延时单位
scheduledExecutorService.schedule(()-> System.out.println(Thread.currentThread().getName()+"-->"+index),5, TimeUnit.SECONDS);
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
执行结果
pool-1-thread-10-->9
pool-1-thread-8-->7
pool-1-thread-3-->2
pool-1-thread-6-->5
pool-1-thread-2-->1
pool-1-thread-1-->0
pool-1-thread-5-->4
pool-1-thread-9-->8
pool-1-thread-4-->3
pool-1-thread-7-->6
*/
Executors.newSingleThreadScheduledExecutor()
- 单例的延时线程池
- 将上一个创建延时线程数量设为1,也可以达到这个目的
@Test
void test05(){
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
System.out.println("添加任务,时间:"+new Date());
scheduledExecutorService.schedule(()->{
System.out.println("线程被执行时间 :"+new Date());
},2,TimeUnit.SECONDS);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
执行结果
添加任务,时间:Wed Nov 16 15:38:55 CST 2022
线程被执行时间 :Wed Nov 16 15:38:57 CST 2022
*/
Executors.newWorkStealingPool()
- jdk1.8之后才提供
- 是抢占式的线程池
- 也就是任务顺序是随机的
ThreadPoolExecutor
-
最原始的创建线程池的方式,包含了七个参数
-
阿里巴巴java开发手册中规定,线程池必须使用这个来创建
-
构造函数如下
public ThreadPoolExecutor(int corePoolSize,//线程数
int maximumPoolSize,//最大线程数,因为它也是可缓存的
long keepAliveTime,//缓存时间
TimeUnit unit,//缓存时间单位
BlockingQueue<Runnable> workQueue,//任务队列
ThreadFactory threadFactory,//线程工厂,用于创建线程,一般用默认即可;
RejectedExecutionHandler handler//拒绝策略;当任务太多来不及处理时,如何拒绝任务;
) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 其中前五个参数尤为重要
//示例
@Test
void test06(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(500,1000,60,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
//建议使用自己构建的线程池
/*
执行线程
threadPoolExecutor.execute();
*/
}
一、任务队列 workQueue
1、直接提交队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;
2、有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现。使ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。
3、无界的任务队列:有界任务队列可以使用LinkedBlockingQueue实现。使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。
4**、优先任务队列:**优先任务队列通过PriorityBlockingQueue实现。它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。
二、拒绝策略
**1、AbortPolicy策略:**该策略会直接抛出异常,阻止系统正常工作;(默认策略)
**2、CallerRunsPolicy策略:**如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
**3、DiscardOledestPolicy策略:**该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
**4、DiscardPolicy策略:**该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;