文章目录
- 一、常见3种创建线程的方式
- (1)方式1:继承Thread类的方式
- (2)方式2:实现Runnable接口的方式
- (3)方式3:通过Callable和Future接口创建线程
- 二、对比三种方式
- (1)对比
- (2)面试题
一、常见3种创建线程的方式
(1)方式1:继承Thread类的方式
☕线程的创建方式一:继承Thread类
<1> 创建步骤:
①创建一个类,继承Thread类。
②重写run方法。
③将需要由线程执行的具体动作写入run方法。
<2>运行步骤:
①创建线程类的对象。
②通过线程对象调用start方法启动线程。
第一种方式很简单,相信大家都会,详细博客在这里:
https://blog.csdn.net/m0_55746113/article/details/135708814?spm=1001.2014.3001.5502
🌱代码
package test2;
/**
* ClassName: MyThread
* Package: test2
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/2/3 0003 17:28
*/
public class MyThreadMainTest {
public static void main(String[] args) {
//3.创建线程类的对象
MyThread1 my1=new MyThread1();
//4.通过线程对象调用start方法启动线程
my1.start();
}
}
//1.创建一个类,继承Thread类
class MyThread1 extends Thread{
//2.重写run方法
@Override
public void run() {
System.out.println("继承Thread类");
}
}
🗃️注意
在start方法里面调用了start0()
,如下:
start0
是什么?
private native void start0();
注意看native
关键字:
也就是说,start0
方法的实现不是Java代码,是C或C++实现的,而C或C++在操作系统当中一般是用来操作驱动程序的。
驱动程序一般是由操作系统调用的,所以start0
执行之后,此时程序的执行就交给了操作系统。操作系统决定让哪个线程的run
方法执行。
start
只是在这里保证线程的启动而已,启动以后的事情就脱离控制了,归操作系统管理。
(2)方式2:实现Runnable接口的方式
☕线程创建方式二:实现Runnable接口
<1> 创建步骤
①创建一个类,实现Runnable接口。
②重写run方法。
③将需要由线程执行的具体动作写入run方法。
<2> 运行步骤
①创建目标对象。
②通过Thread类的构造方法创建线程对象。
③通过线程对象调用start方法启动线程。
详细博客在这里:
https://blog.csdn.net/m0_55746113/article/details/135840102?spm=1001.2014.3001.5502
🗳️【说明】
这里再说详细一下Runnable接口实现的方式。
public class MyThreadMain {
public static void main(String[] args) {
//4.通过线程对象调用start方法来启动线程
MyThread my1=new MyThread();
}
}
//1.创建一个类,实现Runnable接口
class MyThread implements Runnable{
//2.重写run方法
@Override
public void run() { //3.需要由线程执行的具体动作写入run方法
System.out.println("实现Runnable接口的方式创建多线程");
}
}
若按照方式一的步骤,最后一步应该是用对象调用start()
方法,如下:
但是发现没有start()
方法。
🎲为什么没有start()
方法?
注意,start
方法可以启动线程,但是这个start
方法是Thread
类的方法,而Runnable
接口中没有start方法。
如下:
那么start
方法不属于Runnable接口,当MyThread类实现了Runnable接口之后,自然也得不到start方法。
所以上面用实现类的对象调用start方法是不可行的。
🚗现在我们想调用start方法来启动线程,但是start方法是Thread类的。
那么想要调用start方法来调用,就需要使用Thread类的对象来调用。
现在只有MyThread的对象,所以需要将它变成Thread的对象。有什么关系呢?
我们写的MyThread类实现了Runnable接口,而Thread类也实现了Runnable接口。
现在去找Thread类的构造方法,如下:
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
注意看这个构造方法public Thread(Runnable target)
的参数Runnable target
,是Runnable
接口。
当一个方法的参数是接口类型的时候,可以传递这个接口的子类。
MyThread
类是Runnable接口的子类,所以此时MyThread可以传递到参数上,那么就可以通过new Thread()
来创建线程对象。
这时候会发现MyThread变成了Thread,通过构造方法变的。
此时Thread有了,Thread里面有start方法,所以接下来就可以使用th1来调用start方法。
如下:
🌱代码
package test2;
/**
* ClassName: MyThreadMain
* Package: test2
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/2/3 0003 16:27
*/
public class MyThreadMain {
public static void main(String[] args) {
//4.创建线程要执行的目标对象(最终线程要执行的东西)
MyThread my1=new MyThread();
//Thread类的构造方法public Thread(Runnable target)
//5.创建线程对象(Thread是Java提供的线程类,用线程类创建的对象就是线程对象)
Thread th1=new Thread(my1);
//6.通过线程对象调用start方法来启动线程
th1.start();
}
}
//1.创建一个类,实现Runnable接口
class MyThread implements Runnable{
//2.重写run方法
@Override
public void run() { //3.需要由线程执行的具体动作写入run方法
System.out.println("实现Runnable接口的方式创建多线程");
}
}
(3)方式3:通过Callable和Future接口创建线程
🗳️认识几个接口和类
①Callable
接口:(只有一个方法call()
,位于java.util.concurrent包)
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
call()
方法有返回值V,这个返回值是实现接口的时候就决定的。
②Future
接口:(位于java.util.concurrent包)
有一个重要方法get()
③java.util.concurrent.FutureTask
类:
public class FutureTask<V> implements RunnableFuture<V>
RunnableFuture
接口:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
可以看到,FutureTask
类实现了RunnableFuture
接口,RunnableFuture
接口继承了Runnable
接口。
所以得出,FutureTask
类实现了Runnable
接口,那么FutureTask
类就可以拿到Runnable的run方法。
所以接下来创建线程类的时候,让它继承FutureTask
类就可以拿到run
方法。
☕线程创建方式三:通过Callable和Future接口创建线程
<1> 创建步骤
①创建一个类,实现Callable接口。
②重写call()方法。
③将需要由线程执行的具体动作写入call()方法。(可以通过call方法得到线程最终的执行结果)
<2> 运行步骤
①创建目标对象。
②通过FutureTask类的构造方法public FutureTask(Callable<V> callable)
封装目标对象成Runnable的子类对象。–>为了让Callable和Runnable产生关系
③通过Thread类的构造方法public Thread(Runnable target)
创建线程对象。
④通过线程对象调用start方法启动线程。
🚗举例
1、创建一个类,实现Callable
接口
//1.创建一个类,实现Callable接口
class MyThread2 implements Callable<Integer>{ //<>代表call方法的返回值
@Override
public Integer call() throws Exception {
return null;
}
}
注意在实现Callable
的时候要给出这个东西:
方法的返回值就是这个线程运行结果值的类型。
2、重写call
方法
比如打印输出100个数字。
//1.创建一个类,实现Callable接口
class MyThread2 implements Callable<Integer>{ //<>代表call方法的返回值
@Override
public Integer call() throws Exception {
int i=1; //因为最后要返回i,所以这里将i从for里面拿出来
for (; i <=100 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
return i;
}
}
也可以用while来写:
//1.创建一个类,实现Callable接口
class MyThread2 implements Callable<Integer>{ //<>代表call方法的返回值
//2.重写call方法
@Override
public Integer call() throws Exception {
int i=1; //因为最后要返回i,所以这里将i从for里面拿出来
while(i<=100){
System.out.println(Thread.currentThread().getName()+":"+i);
i++;
}
return i;
}
}
现在一个线程操作类就创建好了。
注意:
实现Callable接口的时候需要指定线程执行结果的返回值类型(如果不知道具体返回什么,就写Object也行)。
<>里面的东西决定了这个线程执行以后返回值结果类型。
3、创建目标对象
public class MyThreadMainTest2 {
public static void main(String[] args) {
//3.创建目标对象
MyThread2 my1=new MyThread2();
}
}
4、用FutureTask封装目标对象
FutureTask
类里面有一个构造方法,可以将Callable
封装为FutureTask
。
如下:
private Callable<V> callable;
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
而FutureTask
类实现了Runnable
接口,如下:
所以,我们的类实现了Callable接口,就可以变成一个实现Runnable的类,然后再用Thread类的构造方法将它封装成线程对象。
调用构造方法 public FutureTask(Callable<V> callable)
,并将目标对象传入。
如下:
public class MyThreadMainTest2 {
public static void main(String[] args) {
//3.创建目标对象
MyThread2 my1=new MyThread2();
//4.用FutureTask封装目标对象(因为FutureTask实现了Runnable接口)
FutureTask futureTask=new FutureTask(my1); //调用构造方法 public FutureTask(Callable<V> callable)
}
}
那么futureTask
就能成为Runnable接口的子类了。
所以,接下来要成为Runnable接口的子类的话,需要通过Thread类的构造方法。
5、创建线程对象
通过Thread类的构造方法public Thread(Runnable target)
创建线程对象。
如下:
public class MyThreadMainTest2 {
public static void main(String[] args) {
//3.创建目标对象
MyThread2 my1=new MyThread2();
//4.用FutureTask封装目标对象(因为FutureTask实现了Runnable接口)
FutureTask futureTask=new FutureTask(my1); //调用FutureTask的构造方法 public FutureTask(Callable<V> callable)
//5.创建线程对象
Thread th1=new Thread(futureTask); //通过Thread类的构造方法 public Thread(Runnable target)创建线程对象
}
}
6、启动线程
通过线程对象调用start
方法启动线程。
如下:
public class MyThreadMainTest2 {
public static void main(String[] args) {
//3.创建目标对象
MyThread2 my1=new MyThread2();
//4.用FutureTask封装目标对象(因为FutureTask实现了Runnable接口)
FutureTask futureTask=new FutureTask(my1); //调用FutureTask的构造方法 public FutureTask(Callable<V> callable)
//5.创建线程对象
Thread th1=new Thread(futureTask); //通过Thread类的构造方法 public Thread(Runnable target)创建线程对象
//6.通过线程对象调用start方法启动线程
th1.start();
}
}
🌱代码
package test2;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* ClassName: MyThreadMainTest2
* Package: test2
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/2/3 0003 18:23
*/
public class MyThreadMainTest2 {
public static void main(String[] args) {
//3.创建目标对象
MyThread2 my1=new MyThread2();
//4.用FutureTask封装目标对象(因为FutureTask实现了Runnable接口)
FutureTask futureTask=new FutureTask(my1); //调用FutureTask的构造方法 public FutureTask(Callable<V> callable)
//5.创建线程对象
Thread th1=new Thread(futureTask); //通过Thread类的构造方法 public Thread(Runnable target)创建线程对象
//6.通过线程对象调用start方法启动线程
th1.start();
}
}
//1.创建一个类,实现Callable接口
class MyThread2 implements Callable<Integer>{ //<>代表call方法的返回值
//2.重写call方法
@Override
public Integer call() throws Exception {
int i=1; //因为最后要返回i,所以这里将i从for里面拿出来
while(i<=100){
System.out.println(Thread.currentThread().getName()+":"+i);
i++;
}
return i;
}
}
🔥补充
在线程运行的时候可以对线程做的一些处理。
java.util.concurrent.FutureTask
类:
public boolean cancel(boolean mayInterruptIfRunning)
:是否取消正在执行的线程任务。- false为取消线程任务
public boolean isCancelled()
:判断是否是线程任务没有运行结束之前取消线程。public boolean isDone()
:判断线程任务是否正常执行完毕。public V get()
:得到线程任务的执行结果。
🌱代码
package test2;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* ClassName: MyThreadMainTest2
* Package: test2
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/2/3 0003 18:23
*/
public class MyThreadMainTest2 {
public static void main(String[] args) {
//3.创建目标对象
MyThread2 my3=new MyThread2();
MyThread2 my4=new MyThread2();
//4.用FutureTask封装目标对象(因为FutureTask实现了Runnable接口)
FutureTask futureTask3=new FutureTask(my3); //调用FutureTask的构造方法 public FutureTask(Callable<V> callable)
FutureTask futureTask4=new FutureTask(my4);
//5.创建线程对象
Thread th3=new Thread(futureTask3); //通过Thread类的构造方法 public Thread(Runnable target)创建线程对象
Thread th4=new Thread(futureTask4);
//6.通过线程对象调用start方法启动线程
th3.start();
th4.start();
}
}
//1.创建一个类,实现Callable接口
class MyThread2 implements Callable<Integer>{ //<>代表call方法的返回值
//2.重写call方法
@Override
public Integer call() throws Exception {
int i=1; //因为最后要返回i,所以这里将i从for里面拿出来
while(i<=100){
System.out.println(Thread.currentThread().getName()+":"+i);
i++;
}
return i;
}
}
🍺输出(部分)
【方法演示】
①public boolean cancel(boolean mayInterruptIfRunning)
:是否取消正在执行的线程任务。【false为取消线程任务】
现在让Thread-1
取消执行:
再次输出:
②public boolean isCancelled()
:判断是否是线程任务没有运行结束之前取消线程。
若没有取消线程:
输出:
现在取消Thread-1:
//是否取消正在执行的线程任务
futureTask4.cancel(false);
//判断是否是线程任务没有运行结束之前取消线程
System.out.println(futureTask4.isCancelled());
输出:
③public boolean isDone()
:判断线程任务是否正常执行完毕。
比如:
//public boolean cancel(boolean mayInterruptIfRunning):是否取消正在执行的线程任务
futureTask4.cancel(false);
//public boolean isCancelled():判断是否是线程任务没有运行结束之前取消线程
System.out.println(futureTask4.isCancelled());
//public boolean isDone():判断线程任务是否正常执行完毕
System.out.println(futureTask4.isDone());
输出:(因为线程Thread-1取消了,所以执行完毕,输出true)
若线程没有执行完毕,就会输出false
,如下:
④public V get()
:得到线程任务的执行结果。
//public V get():得到线程任务的执行结果。
try {
int res=(Integer) futureTask4.get();
System.out.println("线程1运行结果为:"+res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
输出:
🌱整体代码
package test2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* ClassName: MyThreadMainTest2
* Package: test2
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/2/3 0003 18:23
*/
public class MyThreadMainTest2 {
public static void main(String[] args) {
//3.创建目标对象
MyThread2 my3=new MyThread2();
MyThread2 my4=new MyThread2();
//4.用FutureTask封装目标对象(因为FutureTask实现了Runnable接口)
FutureTask futureTask3=new FutureTask(my3); //调用FutureTask的构造方法 public FutureTask(Callable<V> callable)
FutureTask futureTask4=new FutureTask(my4);
//5.创建线程对象
Thread th3=new Thread(futureTask3); //通过Thread类的构造方法 public Thread(Runnable target)创建线程对象
Thread th4=new Thread(futureTask4);
//6.通过线程对象调用start方法启动线程
th3.start();
th4.start();
//public boolean cancel(boolean mayInterruptIfRunning):是否取消正在执行的线程任务
//futureTask4.cancel(false);
//public boolean isCancelled():判断是否是线程任务没有运行结束之前取消线程
System.out.println(futureTask4.isCancelled());
//public boolean isDone():判断线程任务是否正常执行完毕
System.out.println(futureTask4.isDone());
//public V get():得到线程任务的执行结果。
try {
int res=(Integer) futureTask4.get();
System.out.println("线程1运行结果为:"+res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1.创建一个类,实现Callable接口
class MyThread2 implements Callable<Integer>{ //<>代表call方法的返回值
//2.重写call方法
@Override
public Integer call() throws Exception {
int i=1; //因为最后要返回i,所以这里将i从for里面拿出来
while(i<=100){
System.out.println(Thread.currentThread().getName()+":"+i);
i++;
}
return i;
}
}
二、对比三种方式
(1)对比
🔥表格对比图
继承Thread类 | 实现Runnable接口 | Callable和Future接口 |
---|---|---|
1、创建类继承Thread类 2、重写run方法 | 1、创建新类实现Runnable接口 2、重写run方法 | 1、创建新类实现Callable接口 2、重写call方法 3、注意Callable接口的泛型类型 |
run方法没有返回值,不能声明抛出异常 | run方法没有返回值,不能声明抛出异常 | call方法有返回值,通过Future接口提供的get方法得到返回值,可以声明抛出异常 |
1、创建Thread类的子类对象【线程对象】 2、通过子类对象调用start方法启动线程 | 1、创建实现类Runnable接口的子类对象【目标对象】 2、通过Thread类的构造方法,关联目标对象,创建线程对象【Thread类的对象】 3、通过线程对象调用start方法启动线程 | 1、创建实现Callable接口的子类对象【目标对象】 2、通过Future接口的子类FutureTask将目标对象包装成Runnable接口的子类对象 3、通过Thread的构造方法,关联FutureTask包装成的Runnable接口的子类对象【Thread类的对象】 4、通过线程对象调用start方法启动线程 |
无法共享资源(和目标对象没有关系) | 可以共享资源 | 可以共享资源 |
不考虑资源共享时 | 考虑资源共享时 | 考虑资源共享时,异步编程 |
(2)面试题
🎲若同时使用Thread的run和Runnable的run方法,会怎样?
先用继承Thread的方式:
new Thread(){
@Override
public void run() {
System.out.println("I am Thread");
}
}.start();
然后用实现Runnable接口的方式,将Runnable接口实现类的对象写入小括号:
🌱代码
package test2;
/**
* ClassName: BothRunnableThread
* Package: test2
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/2/3 0003 12:09
*/
public class BothRunnableThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("I am Runnable");
}
}){
@Override
public void run() {
System.out.println("I am Thread");
}
}.start();
}
}
🍺输出
可以看到,执行的是Thread里面重写的run方法,并没有执行Runnable里面的run方法。
若将run
方法进行重写之后,会将Thread原有的run方法给覆盖:
当原有的Thread中的run方法被覆盖之后,也就是那几行代码已经不见了。
就无法执行到Runnable的重写的run方法了。
☕总结
准确来说,创建线程的方式只有一种,那就是new Thread
。
实现线程的执行单元有2种方式:一种是继承Thread,一种是实现Runnable。