2024-03-23  阅读(41)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1491725168

回答

在 Java 中, 创建线程的方式有四种:

  1. 继承 Thread 类
  2. 实现 Runnable
  3. 实现 Callable 接口,并结合 Future 实现
  4. 通过线程池创建线程

其实,归根到底只有两种,一个是继承 Thread 类,一个是实现 Runnable 接口,其他两种方式,其本质还是这两种方式。

扩展知识

继承 Thread 类

直接继承 Thread 类并重写其 run() 来创建线程。

public class MyThread extends Thread {
     @Override
    public void run() {
        // do something()
    }
}

public class Test {
    public static void main(String[] args) {
        new MyThread().start();
    }
}

这种方式看起来简单,但是它的灵活性较差。由于 Java 不支持多重继承,一旦继承了 Thread 类,就无法继承其他类,导致我们无法对其进行扩展。

实现 Runnable

实现 Runnable 接口 ,并实现 run()

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // do something()
    }
}

public class Test {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
    }
}

相比继承 Thread 类,实现 Runnable 接口更加灵活。

实现 Callable 接口,并结合 Future 实现

  • 定义一个 Callable 的实现类,并实现 call()
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // 线程执行的代码
        // 计算结果并返回
        return 123;
    }
}

有两种方式来执行 Callable。

  • 1、由于 Thread 对象只能执行 Runnable 任务,因此无法直接让 Thread 执行 Callable 任务,但是可以先将 Callable 封装成 FutureTask,而 FutureTask 是实现了 Runnable 接口的,所以 Thread 对象可以执行 FutureTask 任务:
public class ThreadTest {
    public static void main(String[] args){
        // 将Callable封装成FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());

        // 把 FutureTask 作为 Thread 类的参数 ,创建 Thread 线程对象。
        Thread thread = new Thread(futureTask);

        // 启动线程
        thread.start();

        // 通过FutureTask拿到执行结果
        System.out.println(futureTask.get());
    }
}
  • 2、可以利用 ExecutorService 来实现。使用Executors 来创建一个ExecutorService 对象。利用 ExecutorService 的 submit() 来提交 Callable 任务,该方法返回一个Future对象,该对象可以用来获取Callable任务的结果。
public class ThreadTest {
    public void main(String[] args) throws Exception {
        // 创建ExecutorService
        ExecutorService executor = Executors.newFixedThreadPool(1);

        // 提交Callable任务并获取Future
        Future<Integer> future = executor.submit(new MyCallable());

        // 从Future获取结果
        Integer result = future.get();
    }
}

Runnable 和 Callable 的区别是什么?

  • Runnable 的 run() 不返回任何值,而Callable的call()方法可以返回一个值。
  • Runnable的run()方法不能抛出受检查的异常。如果run()方法内部需要处理异常,它必须在方法内部捕获并处理这些异常。而Callable的call()方法可以抛出异常,因此可以将受检查的异常传递给调用者,由调用者来处理。

通过线程池创建线程

在Java中,使用线程池来创建和管理线程是一种比较好的、推荐的方式,线程池能够减少在创建和销毁线程上的开销,并且可以控制并发线程的数量。

我们可以创建线程池,然后将任务提交给线程池来执行:

public class ThreadTest {
    public void main(String[] args) throws Exception {
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);
        // 创建Runnable对象
        Runnable runnable = () -> {
            // do something()
        };
        // 提交任务给线程池
        executor.submit(runnable);
    }
}

题外话

在上面大明哥说过,从本质上来说 Java 创建线程的方式只有两种,一种是继承 Thread 类一种是实现 Runnable 接口,我们从 Thread 类的源码也可以看出:

There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. The other way to create a thread is to declare a class that implements the Runnable interface.

其实说两种、四种、甚至三种(去网上搜索你会发现有些人写的是三种),大明哥觉得都没有错,关键在于自己能否说清楚,讲明白,让面试官信服。

如果换一种方式来问,比如在 Java 中有多少种方式来创建线程,那就比较多了,大明哥简单列几种:

  1. 继承 Thread 类
  2. 实现 Runnable 接口
  3. 匿名内部类
  4. 实现Callabe接口
  5. 定时器(java.util.Timer)
  6. 线程池
  7. 函数式
  8. Spring异步方法
阅读全文
  • 点赞