所谓线程池,其实和字符串常量池,数据库连接池十分相似,就是设定一块区域,提前放好一些已经成型的线程,当需要使用的时候,直接进行调用不需要经历复杂的创建过程,销毁线程时也不需要经历复杂过程,直接将线程返还给线程池即可线程池最大的优势就是减少了每次启动、销毁线程的损耗!
为什么引入线程池后创建/销毁会更加高效?
其实,创建/销毁线程这个操作时由操作系统内核完成的。而从线程池获取/放回用户代码即可实现,无需交给操作系统内核。
解释操作系统内核:
对于操作系统内核,下面我通过一个场景进行简单的介绍。
如上图所示,假设一个银行,有很多人要办业务,这些人大致分为两类,同样也对应两种不同的需求:
总的来讲,用户态中程序的行为是可控的,比较快速整洁。但是,内核态就需要过另外一个人 (操作系统内核) 的手续,就不可控了。
创建一个线程池
如上图所示,这样就创建了一个数目固定为10的一个线程池。需要注意的是,这里划红线的地方,此处的 new 是方法名字的一部分,不是 new 关键字。
这个操作,就是使用某个静态的方法,直接构造一个对象出来(相当于将 new 方法隐藏在这个方法里)
这种方法称之为“工厂方法”,即利用“工厂模式”设计出的方法。
解释工厂模式:
所谓 工厂模式 简单来讲就是,使用普通的方法来代替构造方法创建对象。会出现这种模式的主要原因就是构造方法中有很多不便之处。
例如,现在有个类要表示平面上的一点,如图所示:
此时很明显就出了问题,正常来讲,多个构造方法是通过 “重载” 的形式提供的。但是,我们要明确的是,重载的要求是,方法名相同,参数的 个数/类型 不相同。 在这里很明显都不满足。
因此,这里才需要引入工厂模式,如图:
这样问题就得以解决了。
代码展示:
public static void main(String[] args) {
ExecutorService poll = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
int n = i;
poll.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello"+n);
}
});
}
}
这里的 submit 方法可以给线程池提供若干个任务。
运行之后不难发现,main 线程结束但是整个进程没有结束,在这里,线程池中的线程都是前台线程,因此会阻止进程结束。
这里要注意的是,当前线程池中存放了 1000 个任务。这 10 个线程分别分配执行,执行结束后就到线程池中获取,但是,不一定是平均分配,可能有的多有的少。
思考:
如上图所示,我们知道,这里的 n 就是起到一个中介的作用,但是为什么直接使用 i 却不行?
其实不难理解,这里主线程 for 中的 i 是一个局部变量,主线程执行的速度很快,这里的 for 执行完了,但是当前 run 的任务在线程池中还没有排到,此时 i 已经销毁了
解决这个问题的思路其实也不难理解,这里其实就是一个变量捕获。我们知道,这里的 run 方法属于 runnable。这个方法的执行不是立刻马上,而是在未来的某个节点才去执行。
所以,为了避免作用域的差异,导致后续执行时 i 已经被销毁,于是也就有了变量捕获,即就是在定义 run 方法时,将 i 的值记录下来。后续执行的时候将其复制过去即可。
Executors创建线程的几种方式:
对于创建线程池,大致有下面几种类型:
创建形式示例:
ExecutorService poll = Executors.newFixedThreadPool(10);
理解 ThreadPoolExecutor 构造方法参数
如上图所示,我们可以看到该构造方法中有很多参数,下面我来进行简单的解释。
首先,将创建一个线程池设想成开公司。每一个员工相当于一个线程。
创建形式示例:
ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
根据前面的示例我们可以知道,实现一个线程池内部至少有两大部分。
代码展示
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
class MyThreadPool{
//创建一个阻塞队列
private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();
// n 表示线程的数量
public void MyThreadPool(int n){
for (int i = 0; i < n; i++) {
Thread t = new Thread(()->{
//不断尝试获取队列中的元素
while(true){
try {
Runnable runnable = queue.take();
//调用 runnable 方法中的 run
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
//添加元素到线程池(阻塞队列)
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
}
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool();
//设定线程池的数量
myThreadPool.MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int n = i;
//创建任务放到线程池
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello: "+n);
}
});
}
}
}