导读
线程池相关的知识点是面试中非常高频的问题,掌握线程及线程池相关的知识点也是程序员向高段位进阶的必由之路。由于线程池涉及线程、并发、编程语言内存模型等多方面的知识,历来也不是一块特别好掌握的内容。因此,小码哥决定好好梳理下这方面的知识,希望能够对你有所帮助。在本文中,作者将以JAVA语言中的线程池设计为基础,从原理分析及代码实践两个方面来进行梳理。
线程的概念
在了解线程池的相关的知识之前,我们有必要再次深入理解下线程的基本概念。在这里,也许会有很多同学质疑,线程的基本概念我们都懂,为什么还需要重复提起呢?
在回答这个问题之前,我们还是先回到实际的编程语言中来看看线程到底是什么?以JAVA为例,在JAVA中如何实现一个线程呢?
public class ThreadDemo01 {
public static void main(String args[]) {
//通过匿名内部类的方式创建线程,并且重写其中的run方法
new Thread() {
public void run() {
while (true) {
System.out.println("线程->" + Thread.currentThread().getName() + " 运行中!");
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
通过上面的代码示例,我们知道在JAVA中要实现一个线程可以通过构造Thread类来实现。之后,通过重写run()方法来让线程执行我们想要让它执行的逻辑。然而,为了让线程生效,我们还需要通过调用start()方法来启动它。那么为什么我们重写了run()方法,但是却还需要调用start()方法呢?run()方法和start()方法有什么关系?到底那个方法才是真正代表了线程这个存在呢?
要搞清楚这个问题,需要我们明确“线程的执行单元”与“线程”是两个不同的概念。在JAVA中通过Thread类重写的run()方法是线程的执行单元,而通过调用start()方法才是真正启动了一个线程。这一点对后面我们理解线程池的作用会比较有用,因为只有从概念上剥离线程的执行单元与线程本身才能更深入的理解线程池存在的意义。
为了更加深入的说明这一点,我们可以来具体分析下上面例子中start()方法在JDK中的源码:
public synchronized void start() {
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
在start()方法的源码中,最核心的部分其实就是start0()这个JNI本地方法:
private native void start0();
也就是说在start方法中会调用start0这个本地方法,但是从源码上这么看又看不出start0的具体逻辑。为此,作者特地翻了下JDK的官方文档,其中关于start方法的说明如下:
Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.
上面这句话的意思是:在开始执行这个线程的时候,JVM将会调用该线程的run方法,而实际上run方法是被本地方法start0()调用的。也就是说,在JAVA中由于语言的约定,我们需要在使用线程时重写线程中的执行单元方法来实现业务逻辑,而真正开启线程资源的则