Java特别篇--关于线程创建的三种方式的总结对比

文章目录

  • 一、常见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(),如下:

Java特别篇--关于线程创建的三种方式的总结对比_第1张图片

start0是什么?

private native void start0();

注意看native关键字:

Java特别篇--关于线程创建的三种方式的总结对比_第2张图片

也就是说,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()方法,如下:

Java特别篇--关于线程创建的三种方式的总结对比_第3张图片

但是发现没有start()方法。

为什么没有start()方法?

注意,start方法可以启动线程,但是这个start方法是Thread类的方法,而Runnable接口中没有start方法。

如下:

Java特别篇--关于线程创建的三种方式的总结对比_第4张图片

那么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,通过构造方法变的。

Java特别篇--关于线程创建的三种方式的总结对比_第5张图片

此时Thread有了,Thread里面有start方法,所以接下来就可以使用th1来调用start方法。

如下:

Java特别篇--关于线程创建的三种方式的总结对比_第6张图片


代码

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 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的时候要给出这个东西:

Java特别篇--关于线程创建的三种方式的总结对比_第7张图片

方法的返回值就是这个线程运行结果值的类型。

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接口,如下:

Java特别篇--关于线程创建的三种方式的总结对比_第8张图片

所以,我们的类实现了Callable接口,就可以变成一个实现Runnable的类,然后再用Thread类的构造方法将它封装成线程对象。

调用构造方法 public FutureTask(Callable 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 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 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 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 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 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;
    }
}

输出(部分)

Java特别篇--关于线程创建的三种方式的总结对比_第9张图片


【方法演示】

public boolean cancel(boolean mayInterruptIfRunning) :是否取消正在执行的线程任务。【false为取消线程任务】

现在让Thread-1取消执行:

Java特别篇--关于线程创建的三种方式的总结对比_第10张图片

再次输出:

Java特别篇--关于线程创建的三种方式的总结对比_第11张图片

public boolean isCancelled():判断是否是线程任务没有运行结束之前取消线程。

若没有取消线程:

image.png

输出:

Java特别篇--关于线程创建的三种方式的总结对比_第12张图片

现在取消Thread-1:

//是否取消正在执行的线程任务
futureTask4.cancel(false);

//判断是否是线程任务没有运行结束之前取消线程
System.out.println(futureTask4.isCancelled());

输出:

Java特别篇--关于线程创建的三种方式的总结对比_第13张图片

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)

Java特别篇--关于线程创建的三种方式的总结对比_第14张图片

若线程没有执行完毕,就会输出false,如下:

Java特别篇--关于线程创建的三种方式的总结对比_第15张图片

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();
}

输出:

Java特别篇--关于线程创建的三种方式的总结对比_第16张图片


整体代码

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 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接口实现类的对象写入小括号:

Java特别篇--关于线程创建的三种方式的总结对比_第17张图片

代码

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();
    }
}

输出

Java特别篇--关于线程创建的三种方式的总结对比_第18张图片

可以看到,执行的是Thread里面重写的run方法,并没有执行Runnable里面的run方法。

若将run方法进行重写之后,会将Thread原有的run方法给覆盖:

Java特别篇--关于线程创建的三种方式的总结对比_第19张图片

当原有的Thread中的run方法被覆盖之后,也就是那几行代码已经不见了。

就无法执行到Runnable的重写的run方法了。

☕总结

准确来说,创建线程的方式只有一种,那就是new Thread

实现线程的执行单元有2种方式:一种是继承Thread,一种是实现Runnable

你可能感兴趣的:(Java基础,java,线程创建的三种方式,继承Thread类,实现Runnable接口,Callable和Future,多线程)