1.学习了解线程池,首先我们必须要先了解线程池的接口类图:
在上边的类图中,最上层就是Executor框架,它是一个根据一组执行策略的调用调度执行和控制异步任务的框架,目的是提供一种将任务提交与任务如何运行分离开的机制。它包含了三个executor接口:
- Executor:运行新任务的简单接口
- ExecutorService:扩展了Executor,添加了用来管理执行器生命周期和任务生命周期的方法
- ScheduledExecutorService:扩展了ExecutorService,支持Future和定期执行任务
在类图中,我们最常使用的是ThreadPoolExecutor和Executors,这两个类都可以创建线程池,其中ThreadPoolExecutor是可定制化的去创建线程池,而Executors则属于是工具类,该类中已经封装好了一些创建线程池的方法,直接调用相应的方法即可创建线程。(来源:https://blog.51cto.com/zero01/2306857)
注:在本文中并不打算深入概念,而是通过实例加注释的方式先让我们先了解线程池创建的一种方式后续会继续深入。
2.如何创建一个无界(没有任何任务限制线程池的默认大小为:2147483647)线程池呢?
实例:
package com.springboot.thread;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestMyThreadPool {
public static void main(String[] args) {
/*创建无界线程池*/
ExecutorService executorService = Executors.newCachedThreadPool();
/*向线程池中加入一个线程*/
executorService.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("Runable1 begin:"+System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("AAAA");
System.out.println("Runable1 end:"+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
/*向线程池中再加入一个线程*/
executorService.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("Runable2 begin:"+System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("BBB");
System.out.println("Runable2 end:"+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
运行结果:
Runable1 begin:1571987371327
Runable2 begin:1571987371331
AAAA
Runable1 end:1571987372327
BBB
Runable2 end:1571987372331
分析运行结果:从线程的开始时间可以认为是同时开始,他们是独立运行的,目前线程池里线程的个数为二。
判断如下代码中的线程池有几个线程?
public class TestMyThreadPool {
public static void main(String[] args) {
/*创建无界线程池*/
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i=0;i<5;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":"+" runing!");
}
});
}
}
}
试验结果:
pool-1-thread-2: runing!
pool-1-thread-1: runing!
pool-1-thread-5: runing!
pool-1-thread-4: runing!
pool-1-thread-3: runing!
也可能如下:
pool-1-thread-2: runing!
pool-1-thread-3: runing!
pool-1-thread-2: runing!
pool-1-thread-4: runing!
pool-1-thread-1: runing!
如图:
从上面的结果我们可以看出,线程池中的线程2被调用了两次 ,为什么会出现这样的结果呢?
先来看源代码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
源代码注释:
Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks. Calls to {@code execute} will reuse previously constructed threads if available. If no existing thread is available, a new thread will be created and added to the pool.
这段话翻译一下就是:
创建一个线程池,该线程池根据需要创建新线程,但是将在先前构造的线程可用时重用它们。 这些池通常将提高执行许多短期异步任务的程序的性能。 调用{@code execute}将重用以前构造的线程(如果有)。 如果没有可用的现有线程,则将创建一个新线程并将其添加到池中。
再看到:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
看到其中一个参数,keepAliveTime(线程存活时间),正是有了这个参数才会出现上面的结果。
总结一下:线程池中如果有存活且空闲的线程那么该线将会被重用。这也是我们使用线程池管理而不用手动去创建线程的原因之一。