Runnable接口
我们看Thread类的定义知道,Thread类实现了Runnable接口
Runnable接口的定义如下:
它只有一个抽象方法run。说明需要其实现类重写这个run方法,而run()之间的内容,就是我们期望这个线程该干什么。
同时Runnable接口还被@FunctionalInterface注解标注,说明它是一个函数式接口。这意味着,可以使用Lambda表达式来创建Runnable接口的实例。
线程创建
在Java中,创建一个线程的方式,有且仅有一种方式:
创建一个Thread类实例,并调用它的start方法。
Thread的构造函数
Thread的构造函数有8个,主要就是给四个参数赋不同的值,这四个参数分别是:
① ThreadGroup g
(线程组)
② Runnable target
(Runnable对象)
③ String name
(线程的名字)
④ long stackSize
(为线程分配的栈的大小,若为0则表示忽略这个参数)
我们最常用的就是 ②③
对于线程的名字,其默认值为:“Thread- ” +nextThreadNum()。
nextThreadNum():
就是一个简单的递增计数器,如果我们创建线程时没有指定线程名,那线程名就会是:Thread-0,Thread-1,Thread-2....
至此,虽然Thread的构造函数很多,但对我们来说,真正的参数只有一个:
Runnable target(Runnable对象)
前面我们说过Thread类实现了Runnable接口,所以它必定会重写run(),我们来看一下,Thread类中的run():
可以看到,其实在Thread类的run(),还是调用了Runnable的run(),即如果我们在创建线程时,没有传入target,则这个run方法就什么也不会做。
启动线程
在创建完线程后,我们就要启动线程,启动一个线程必须调用线程的start()
start()方法本质调用了start0(),start0()方法是一个本地方法,这个方法使得线程开始执行,并由JVM来执行这个线程的run方法,结果就是有两个线程在并发执行,一个是当前线程,也就是调用了Thread类中的start方法的线程,另一个线程就是当前你Thread对象代表的线程, 它执行了run()。
也就是说,这个Thread类实例代表的线程最终会执行它的run方法。
这个时候就出现了问题:
当我们调用start()后,到最后还是执行了run(),那为什么我们一开始不直接调用Thread类中的run或target对象的run()呢?
这样做是为了使用我们的多线程。Thread类从定义上看就是我们的一个普通的类,是什么东西让一个普通的类变成能创建多线程的类呢?
是native方法!
如果我们直接调用Thread类中的run()或target中的run(),仅仅是一个普通的调用,但是如果我们调用了start(),start方法内部会调用一个本地方法start0(),它将导致一个新的线程被创建出来,而我们的Thread实例,就代表了这个新创建出来的线程,并由这个新创建出来的线程来执行Thread实例的run()。