线程作为CPU调度的最小单位,它属于程序进程的子集,关于程序进程和线程的介绍,可以参考《详解操作系统进程》和《详解操作系统线程》两篇文章,这篇文章主要介绍Java线程的相关原理。
一、线程创建
从实现上来说,Java提供了三种创建线程的方式,但从原理上来看,其实只有一种方式,我们先从实现上来简单介绍一下这三种方式
1.1 继承Thread类
直接创建一个ThreadTest的实例,调用它的start()方法就可以创建一个线程了
class ThreadTest extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
1.2 实现Runnable接口
如果只是简单的实现了Runnable接口,它与线程并没有任何关系,只是相当于创建了一个线程执行的任务类而已,要想真正的创建线程,还是需要创建一个Thread对象,把RunnableTest实例作为构造方法的入参
class RunnableTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class CreateThreadTest {
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread thread = new Thread(runnableTest);
thread.start();
}
}
1.3 实现Callable接口
与Runnable很相似,它相当于也是也个任务的实现类,需要结合线程池的submit()方法才能使用,但与Runnable最本质的区别是,Callable的call()方法可以有返回值
class CallableTest implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return ThreadLocalRandom.current().nextInt();
}
}
public class CreateThreadTest {
public static void main(String[] args) {
CallableTest callableTest = new CallableTest();
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future<Integer> future = executorService.submit(callableTest);
}
}
1.4 Lambda表达式
这种方式与第二种方式其实是一样的,只是写法比较简洁明了
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
二、线程实现原理
2.1 只有一种线程创建方式
我们前面说过从实现原理上来讲,创建线程只有一种方式,我们从源码上来分析这种说法
继承Thread和实现Runnable接口两种方式本身就是一种方式,通过创建Thread实例,然后调用start()方法来创建实例
我们先主要看一下Callable接口实现类的使用,我们具体看一下ExecutorService的submit()方法
在submit()方法中,首先将Callable实例封装成一个FutureTask实例,FutureTask实现了RunnableFuture接口,而RunnableFuture又实现了Runnable接口,也就是说封装后的FutureTask仍然只是一个任务实例,此时与线程并没有任何关系,真正建立关系是在execute()方法中
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
execute()方法是线程池的核心方法,该方法在后面介绍线程池的文章中会对其进行详细介绍,现在我们主要看它的addWorker()方法,该方法就是去创建一个线程
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
……
}
在addWorker()方法中,会去创建一个Worker实例,而在Worker的构造方法中,会去创建一个Thread实例
private boolean addWorker(Runnable firstTask, boolean core) {
……
w = new Worker(firstTask);
final Thread t = w.thread;
……
}
首先会去拿到一个ThreadFactory实例,我们以DefaultThreadFactory为例,看下newThread()方法的实现,就是去创建了一个Thread实例
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
总结:从上面对Callable的分析,我们可以得出结论,所有创建线程的方式都可以归结为一种方式,那就是创建Thread实例
那么问题就来了,我们创建了一个Thread实例,就完成了线程的创建吗?那我们的run()和start()方法又有什么区别呢?带着这样的问题,我们深入JVM和操作系统层面,来看一个Java线程创建的过程到底是什么样的
2.2 线程创建原理
2.2.1 run()与start()
首先我们先看一下run()和start()方法的区别,如果我们自定义一个类ThreadTest,然后继承了Thread类,可以选择是否重写run()方法,这个时候,我们创建了一个ThreadTest的实例,当我们用这个实例去调用run()方法时,这就是一个简单的方法调用,与线程没有任何关系
class ThreadTest extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
同样如果我们实现了Runnable接口,先创建一个Runnable接口的实例,然后作为构造方法入参创建一个Thread实例
class RunnableTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class CreateThreadTest {
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread thread = new Thread(runnableTest);
}
}
在构造方法中会去调用init()初始化方法,初始化方法中把Runnable的实例存在了Thread实例的target属性中
当调用Thread实例的run()方法时,就是简单的去调用Runnable实例的run()方法,也与线程的创建没有关系,只是普通的方法调用
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
……
this.target = target;
……
}
public void run() {
if (target != null) {
target.run();
}
}
下面我们看一下start()方法的源码,在start()方法中,会去调用本地方法start0(),这个方法才是真正去创建一个线程
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
2.2.2 线程创建流程
在Thread初始化的时候,首先会去调用本地方法registerNatives(),这个方法的主要作用是绑定线程相关的本地方法和真正JVM方法之间的映射关系
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
}
JNINativeMethod中建立了JNI的映射关系