线程创建方式-继承 Thread 类、实现 Runnable 接口、Callable接口

在 Java 中,有多种方式来实现多线程。下面通过实例来分别介绍继承 Thread 类、实现 Runnable 接口、Callable接口来创建线程。

1、继承 Thread 类

1.1类图

先来看下Thread的类图:

在这里插入图片描述
​ 从类图中可以看出,Runnable接口是一个函数式接口,里面只有一个抽象的run()方法,Thread 类本质上就是实现了 Runnable 接口的一个实例。

​ 启动线程的唯一方法就是通过 调用Thread类的 start()方法。start()方法是一个 native 方法,它会启动一个新线程,并执行 run()方法。

​ 这种方式实现多线程很简单,通过自己的类直接 extend Thread,并复写 run()方法,就可以启动新线程并执行自己定义的 run()方法。

对于线程的启动过程,会在下一章节进行详细讲解。

1.2 实例

public class ThreadDemo extends Thread {

    @Override
    public void run() {
        System.out.println("运行线程-->"+Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo1 = new ThreadDemo();
        ThreadDemo threadDemo2 = new ThreadDemo();

        threadDemo1.start();
        threadDemo2.start();

    }
}

2、实现 Runnable 接口

如果自己的类已经 extends 另一个类,就无法直接 继承Thread,此时,可以实现一个 Runnable 接口。定义runnable接口的实现类,并重写该接口的run()方法。

实现方式如下:

public class RunnableDemo implements Runnable{
    @Override
    public void run() {
        System.out.println("运行线程-->"+Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        Thread t1=new Thread(new RunnableDemo());
        t1.start();
        System.out.println("运行线程-->"+Thread.currentThread().getName());
    }
}

3、实现 Callable 接口

3.1 类图

在这里插入图片描述

加入了Thread继承关系的类图:

在这里插入图片描述

通过类图可以清楚的看出,FutureTask类实现了RunnableFuture接口,RunnableFuture又继承了RunnableFuture两个接口。

​ Callable对象不能直接作为Thread对象的target,因为Callable接口是 Java 5 新增的接口,不是Runnable接口的子接口。对于这个问题的解决方案,就引入 Future接口,此接口可以接受call() 的返回值,RunnableFuture接口是Future接口和Runnable接口的子接口,可以作为Thread对象的target 。并且, Future 接口提供了一个实现类:FutureTask 。

FutureTask实现了RunnableFuture接口,可以作为 Thread对象的target。

​ 实现 Callable 接口可以通过 Future Task 包装器来创建 Thread 线程,有的时候,我们可能需要让一步执行的线程在执行完成以后,提供一个返回值给到当前的主线程,主线程需要依赖这个值进行后续的逻辑处理,那么这个时候,就需要用到带返回值的线程了。

简单理解一下就是:这是一个可以带返回值的线程。

3.2实例

public class CallableDemo implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("运行线程-->"+Thread.currentThread().getName());
        return "SUCCESS";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableDemo2 callableDemo = new CallableDemo2();
        FutureTask<String> futureTask = new FutureTask<>(callableDemo2);
        new Thread(futureTask).start();
        Thread.sleep(100);
        System.out.println(futureTask.get());
    }
}

4、Runnable和Callable区别

通过上述三种方式,其实可以归为两类:继承类和实现接口两种方式。相比继承, 接口实现可以更加灵活,不会受限于Java的单继承机制。并且通过实现接口的方式可以共享资源,适合多线程处理同一资源的情况。下面简单介绍一下两种接口类实现的区别:

  • 1)Callable规定的方法是call(),Runnable规定的方法是run();

  • 2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得;

  • 3)call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常;

  • 4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

5、start()和run()的区别

  • start()方法用来开启线程,但是线程开启后并没有立即执行,他需要获取cpu的执行权(分配的时间片)才可以执行;
  • run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)。

总结:

本篇主要介绍了线程的三种创建方式,下一篇来介绍一下线程的启动过程,一个线程到底是怎么创建出来,又是怎么启动运行的呢,在下一篇你将会找到答案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章