菜鸡一只~
其实我还是知道我自己的水平的,菜是原罪,还是要不断的学习成长提高啊!
因此我会看看一些乱七八糟的直播啊,博客啊之类的(虽然往往整整2个小时的直播里,可能就讲了15分钟的重点,不过有时候会提到某项技术,大概的实现方式和适用的场景,会让我眼前一亮啊),当发现有一两个点我之前没听说过或者我觉得有意思,我就会记录下来,等有时间的时候整理成自己的知识!
本文要说的是在java线程池的一些使用和概念(高并发场景下的合并请求操作!)
当然这样的文章百度还是可以搜索到很多的,我只是自己再总结归纳下,整理成自己的知识~
概念:
大家应该都知道数据库连接池这样的概念,在一个集合里面创建多个数据库连接(当然大家做web项目的时候基本都是用各种各样连接池工具,比如Druid,基本不自己写),线程池也是这样的类似的概念,在池子里面维护多个线程,当有任务的时候,传入到线程池中执行,那么就会有这么几个问题了!
问题:
1、如何创建线程池才合理?
2、线程池执行任务的方式?
3、执行的任务是否有返回值,如何接收?
回答:
1、java提供的创建线程池的类:ThreadPoolExecutor
该类的开头有大量的注释,对于线程池的解释,大家有兴趣可以好好研究下!
//ThreadPoolExecutor最全的构造方法
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
-1.参数说明
线程池核心线程数量:corePoolSize,
线程池最大线程数量: maximumPoolSize,
核心线程空闲存活时长:keepAliveTime,
存活时长的时间单位:TimeUnit unit,
当任务数量大于核心线程数量之后,先把数据放入队列:BlockingQueue<Runnable> workQueue,
如何创建线程(是否自定义):ThreadFactory threadFactory,
当线程池已经达到最大线程数量,并且队列也满了之后,如何处理新来的任务:RejectedExecutionHandler handler
-2.创建方式
大家会看到有两种方式,
一种是Executors有一些静态的方法可以直接调用,例如:
// 定时任务线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
-3.java提供的线程池有哪些?
newSingleThreadExecutor
创建一个单线程的线程池。 任何时候这个线程池都只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool
这个是比较常用的,创建固定大小的线程池,大家需要按照各自项目提交线程任务的多少和每个任务占用时长来设置该线程池的大小。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小(该线程池核心线程数和最大线程数相等)。线程池的大小一旦达到最大值就会保持不变(因为keepAliveTime为0),如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool
创建核心线程数为0,并且一个可复用之前创建但是现在空闲的线程的线程池,但是因为最大线程数是:Integer.MAX_VALUE
因此,如果任务过多,并且占用时间长,就会导致堆外内存溢出:https://blog.csdn.net/oufua/article/details/79237458
newScheduledThreadPool
创建一个线程池支持定时以及周期性执行任务的需求,通过scheduleAtFixedRate或者scheduleWithFixedDelay来设定如何调度,可以用来积累一定的请求或者数据,然后批量执行
newWorkStealingPool
jdk1.8之后多出的线程池,该线程池可以自动调用系统可用的线程数(优点:实际线程数会动态地生长和收缩),并行的执行任务,但是保证不了任务执行有序!有些博客认为,该线程池适合用在耗时相对较长的任务(我觉得说的有道理,但是没有实际用过,因此。。。我就不瞎说了)
-4.注意
使用任何的线程池,基本上都是ThreadPoolExecutor的不同参数的实现,因此理解他的参数至关重要,这也是为什么网上有些文章说,不要随便使用Executors来构建线程池的原因,因为如果设置不当,当任务过多的时候,可能会导致oom而使得java应用挂掉!
2、线程的执行方式
我们需要关注:
并行度(线程池的线程个数corePoolSize、 maximumPoolSize,),任务是否立刻执行(是否使用newScheduledThreadPool),提交的任务数量多于线程池最大数量如何处理新来的任务(RejectedExecutionHandler)
3、是否有返回值
我先总体说一下,如果执行线程如果不需要返回值,那只需要创建一个Runnable,然后实现run方法,让线程调用就ok了
如果需要返回值,就需要好好看看Callable和Future
-1.无返回值
如果运行的任务没有返回值,那就比较简单了,可以直接执行任务把数据跑到数据库中等待查询,或者执行任务结束的时候往消息队列中发送一条消息,告诉消费者某条任务执行完毕,可以通知相应的用户
-2.有返回值
如果有返回值,那就需要在发送任务到线程池的线程等待返回结果,因此就需要用到另一套东西了
Callable和Future兄弟,前者是用来设置如何产生结果,后者用来获取结果
看下面几个例子大家应该就会明白(具体看代码中的注释):
//创建一个Callable,指定call方法中如何执行,如何返回结果
Callable<String> callable = new Callable<String>() {
public String call() throws Exception {
System.out.println("This is ThreadPoolExetor#submit(Callable<T> task) method.");
return "result";
}
};
//如何获得返回结果,跟如何创建线程有关系
//如果是通过 new Thread().start()或者线程池的execute()这种没有返回值的方法,我们就需要一个Future对象,来调用get()获取结果
FutureTask<String> task = new FutureTask<String>(callable);
new Thread(task).start();
//获取结果
String result = task.get();
//如果是通过线程池的submit方法,会返回Future<T> future,就可以通过future.get()来获得返回值!
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(callable);
System.out.println(future.get());
//submit中有submit(Runnable task, T result),可以传入一个result,如果调用正常,并且执行完毕就会返回result出来(有点像返回默认值的机制),比如:
class Task implements Runnable {
@Override
public void run() {
System.out.println("This is ThreadPoolExetor#submit(Runnable task, T result) method.");
}
}
//打印结果:This is ThreadPoolExetor#submit(Runnable task, T result) method.
//打印结果:success
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new Task(), "success");
System.out.println(future.get());
好,希望本文能给不少对于线程池有疑惑的同学带来一些启发,线程池在工作中还是比较常用到的,特别对于一些数据库的长任务,或者高并发的场景!
当然在撰写该文的时候,我也找到了几篇不错的文章,在这里分享出来~
ThreadPoolExecutor中的submit()方法详细讲解:https://blog.csdn.net/qq_33689414/article/details/72955253(作者:张行之)
线程池详解:https://www.cnblogs.com/CarpenterLee/p/9558026.html(作者:CarpenterLee)
彻底理解Java的Future模式:https://www.cnblogs.com/cz123/p/7693064.html(作者:大诚挚)
异步编程CompletableFuture实现高并发系统优化之请求合并:https://www.cnblogs.com/itbac/p/11298626.html(作者:北溪)
好了本文就到这里,其实有点长了,有什么问题欢迎大家留言!!!