继承Thread类
定义一个线程类,重写实现run方法
(因为 Thread类也实现了 Runable接口),在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。
当调用 start()方法
启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的run()方法
。
注:启动该线程要调用该线程的start方法,而不是run方法!!!
// 模板代码
public class MyThread extends Thread {
public void run() {
// ...
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
// 完整示例
package thread;
/**
* 多线程
* 线程:程序中一个单一的顺序执行流程
* 多线程:多个单一顺序执行流程"同时"执行
*
* 多线程改变了代码的执行方式,从原来的单一顺序执行流程变为多个执行流程"同时"执行。
* 可以让多个代码片段的执行互不打扰。
*
* 线程之间是并发执行的,并非真正意义上的同时运行。
* 常见线程有两种方式:
* 1:继承Thread并重写run方法
*
*/
public class ThreadDemo1 {
public static void main(String[] args) {
//创建两个线程
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
/*
启动线程,注意:不要调用run方法!!
线程调用完start方法后会纳入到系统的线程调度器程序中被统一管理。
线程调度器会分配时间片段给线程,使得CPU执行该线程这段时间,用完后
线程调度器会再分配一个时间片段给一个线程,如此反复,使得多个线程
都有机会执行一会,做到走走停停,并发运行。
线程第一次被分配到时间后会执行它的run方法开始工作。
*/
t1.start();
t2.start();
}
}
/**
* 第一种创建线程的优点: 结构简单,利于匿名内部类形式创建。
*
* 缺点:
* 1:由于java是单继承的,这会导致继承了Thread就无法再继承其他类去复用方法
* 2:定义线程的同时重写了run方法,这等于将线程的任务定义在了这个线程中导致线程只能干这件事。重(chong)用性很低。
*/
class MyThread1 extends Thread{
public void run(){
for (int i=0;i<1000;i++){
System.out.println("hello姐~");
}
}
}
class MyThread2 extends Thread{
public void run(){
for (int i=0;i<1000;i++){
System.out.println("来了~老弟!");
}
}
}
实现Runnable接口
需要实现run()方法
。通过 Thread 调用start()方法
来启动线程。
// 模板代码
public class MyRunnable implements Runnable {
public void run() {
// ...
}
}
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
// 完整示例
package thread;
/**
* 第二种创建线程的方式
* 实现Runnable接口单独定义线程任务
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//实例化任务
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyRunnable2();
//创建线程并指派任务
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
class MyRunnable1 implements Runnable{
public void run() {
for (int i=0;i<1000;i++){
System.out.println("你是谁啊?");
}
}
}
class MyRunnable2 implements Runnable{
public void run() {
for (int i=0;i<1000;i++){
System.out.println("开门!查水表的!");
}
}
}
实现Callable接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
// 模板代码
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
匿名内部类形式的线程创建
package thread;
/**
* 使用匿名内部类完成线程的两种创建
*/
public class ThreadDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
for(int i=0;i<1000;i++){
System.out.println("你是谁啊?");
}
}
};
/*Runnable r2 = new Runnable() {
public void run() {
for(int i=0;i<1000;i++){
System.out.println("我是查水表的!");
}
}
};*/
//Runnable可以使用lambda表达式创建
Runnable r2 = ()->{
for(int i=0;i<1000;i++){
System.out.println("我是查水表的!");
}
};
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
实现接口 VS 继承Thread
实现接口会更好一些,因为:
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大
到底有几种创建线程的方式?
我们先看下 JDK 源码中对 Thread 类的一段解释,如下图。
这里说的两种方式就是 继承Thread类 和 实现Runnable接口 两种方式。但是,我们会发现这两种方式,最终都会调用Thread.start方法
,而 start方法
最终会调用run方法
。
不同的是,在实现 Runnable 接口的方式中,调用的是 Thread 本类的 run 方法。我们看下它的源码,
这种方式,会把创建的 Runnable 实现类对象赋值给 target ,并运行 target 的 run 方法。
再看继承Thread类的方式,我们同样需要调用 Thread 的 start 方法来启动线程。由于子类重写了 Thread 类的 run 方法,因此最终执行的是这个子类的 run 方法。
所以,我们也可以这样说。在本质上,创建线程只有一种方式,就是构造一个Thread类(其子类其实也可以认为是一个 Thread 类)。
而构造 Thread类 又有两种方式,一种是继承 Thread 类,一种是实现 Runnable接口。其最终都会创建 Thread 类(或其子类)的对象。
再来看实现 Callable ,结合 Future 和 FutureTask 的方式。可以发现,其最终也是通过new Thread(task)
的方式构造 Thread 类。
最后,在线程池中,我们其实是把创建和管理线程的任务都交给了线程池。而创建线程是通过线程工厂类 DefaultThreadFactory 来创建的(也可以自定义工厂类)。我们看下这个工厂类的具体实现。
它会给线程设置一些默认值,如线程名称,线程的优先级,线程组,是否是守护线程等。最后还是通过new Thread()
的方式来创建线程的。
因此,综上所述。在回答这个问题的时候,我们可以说本质上创建线程就只有一种方式,就是构造一个 Thread 类。
如果你要说有 1种、2种、3种、4种 其实也是可以的。重要的是,你要能说出你的依据,讲出它们各自的不同点和共同点。讲得头头是道,让面试官对你频频点头。
说只有构造 Thread 类这一种创建线程方式,个人认为还是有些牵强。因为,无论你从任何手段出发,想创建一个线程的话,最终肯定都是构造 Thread 类。(包括以上几种方式,甚至通过反射,最终不也是 newInstance 么)。
那么,如果按照这个逻辑的话,我就可以说,不管创建任何的对象(Object),都是只有一种方式,即构造这个对象(Object) 类。这个结论似乎有些太过无聊了,因为这是一句非常正确的废话。
以 ArrayList 为例,我问你创建 ArrayList 有几种方式。你八成会为了炫耀自己知道的多,跟我说,
1.通过构造方法,List list = new ArrayList();
2.通过 Arrays.asList(“a”, “b”);
3.通过Java8提供的Stream API,如 List list = Stream.of(“a”, “b”).collect(Collectors.toList());
4.通过guava第三方jar包,List list3 = Lists.newArrayList(“a”, “b”);
等等,仅以上就列举了四种。现在,我告诉你创建 ArrayList 就只有一种方式,即构造一个 ArrayList 类,你抓狂不。
参考
https://cloud.tencent.com/developer/article/1739160