【java】Java并发编程--Java实现多线程的4种方式

文章目录

  • 介绍
  • 继承Thread类创建线程
  • 实现 Runnable 接口创建线程
  • 实现 Callable 接口
  • 使用ExecutorService、Callable、Future实现有返回结果的线程(线程池方式)
  • 其他创建线程的方式

介绍

在Java中,多线程主要的实现(创建线程)方式有四种:

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口
  • 通过FutureTask包装器来创建Thread线程使用ExecutorService、Callable、Future实现有返回结果的多线程
    其中前两种方式线程执行完后都没有返回值,而后两种是带返回值的。
    除此之外,通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务。

继承Thread类创建线程

Thread类本质上也是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程比较简单,通过继承Thread类并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

CreateThreadDemo1.java

public class CreateThreadDemo1 extends Thread {

    public CreateThreadDemo1(String name) {
        // 设置当前线程的名字
        this.setName(name);
    }

    @Override
    public void run() {
        System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) throws Exception {
        // 注意这里,要调用start方法才能启动线程,不能调用run方法
        new CreateThreadDemo1("MyThread1").start();
        new CreateThreadDemo1("MyThread2").start();

    }

}

输出结果:

当前运行的线程名为: MyThread1
当前运行的线程名为: MyThread2

【java】Java并发编程--Java实现多线程的4种方式_第1张图片

实现 Runnable 接口创建线程

由于Java是单继承机制,如果自己的类已经继承自另一个类,则无法再直接继承Thread类,此时,可以通过实现Runnable接口来实现多线程。

实现Runnable接口并实现其中的run方法,然后通过构造Thread实例,传入Runnable实现类,然后调用Thread的start方法即可开启一个新线程。

CreateThreadDemo2.java

public class CreateThreadDemo2 implements Runnable {

    @Override
    public void run() {
        System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) throws Exception {
        CreateThreadDemo2 runnable = new CreateThreadDemo2();
        new Thread(runnable, "MyThread1").start();
        new Thread(runnable, "MyThread2").start();

    }

}

输出结果:

当前运行的线程名为: MyThread1
当前运行的线程名为: MyThread2

实现 Callable 接口

Callable 接口只有一个 call() 方法,源码如下:

public interface Callable<V> {
    V call() throws Exception;
}

从源码我们可以看到 Callable 接口和 Runnable 接口类似,它们之间的区别在于 run() 方法没有返回值,而 call() 方法是有返回值的。

通过实现 Callable 接口实现多线程的步骤如下:

  • 创建 MyCallable 类实现 Callable 接口。
  • 创建 MyCallable 类的实例对象 myCallable。
  • 把实例对象 myCallable 作为参数来创建 FutureTask 类的实例对象 futureTask。
  • 把实例对象 futureTask 作为参数来创建 Thread 类的实例对象 thread,实例对象 thread 就是一个新线程。
  • 调用 start() 方法,启动线程。

代码示例如下:

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int a = 6;
        int b = 9;
        System.out.println("我是通过实现 Callable 接口创建的多线程,我叫" + Thread.currentThread().getName());
        return a + b;
    }
}

class TestMyCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("返回值为:" + futureTask.get());
    }
}

执行结果如下:

我是通过实现 Callable 接口创建的多线程,我叫Thread-0
返回值为:15

FutureTask 类提供了一个 get() 方法用来获取 call() 方法的返回值,但需要注意的是调用这个方法会导致程序阻塞,必须要等到线程结束后才会得到返回值。

使用ExecutorService、Callable、Future实现有返回结果的线程(线程池方式)

ExecutorService、Callable、Future三个接口都是属于Executor框架。可返回值的任务必须实现Callable接口。通过ExecutorService执行Callable任务后,可以获取到一个Future的对象,在该对象上调用get()就可以获取到Callable任务返回的结果了。

注意:Future的get方法是阻塞的,即:线程无返回结果,get方法会一直等待。

CreateThreadDemo4.java

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CreateThreadDemo4 {
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("---- 主程序开始运行 ----");
        Date startTime = new Date();
        
        int taskSize = 5;
        // 创建一个线程池,Executors提供了创建各种类型线程池的方法,具体详情请自行查阅
        ExecutorService executorService = Executors.newFixedThreadPool(taskSize);
        
        // 创建多个有返回值的任务
        List<Future> futureList = new ArrayList<Future>();
        for (int i = 0; i < taskSize; i++) {
            Callable callable = new MyCallable(i);
            // 执行任务并获取Future对象
            Future future = executorService.submit(callable);
            futureList.add(future);
        }
        
        // 关闭线程池
        executorService.shutdown();

        // 获取所有并发任务的运行结果
        for (Future future : futureList) {
            // 从Future对象上获取任务的返回值,并输出到控制台
            System.out.println(">>> " + future.get().toString());
        }

        Date endTime = new Date();
        System.out.println("---- 主程序结束运行 ----,程序运行耗时【" + (endTime.getTime() - startTime.getTime()) + "毫秒】");
    }
}

class MyCallable implements Callable<Object> {
    private int taskNum;

    MyCallable(int taskNum) {
        this.taskNum = taskNum;
    }

    public Object call() throws Exception {
        System.out.println(">>> " + taskNum + " 线程任务启动");
        Date startTime = new Date();
        Thread.sleep(1000);
        Date endTime = new Date();
        long time = endTime.getTime() - startTime.getTime();
        System.out.println(">>> " + taskNum + " 线程任务终止");
        return taskNum + "线程任务返回运行结果, 当前任务耗时【" + time + "毫秒】";
    }
}

输出结果:

---- 主程序开始运行 ----
>>> 0 线程任务启动
>>> 1 线程任务启动
>>> 2 线程任务启动
>>> 3 线程任务启动
>>> 4 线程任务启动
>>> 0 线程任务终止
>>> 1 线程任务终止
>>> 0线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 1线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 4 线程任务终止
>>> 3 线程任务终止
>>> 2 线程任务终止
>>> 2线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 3线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 4线程任务返回运行结果, 当前任务耗时【1001毫秒】
---- 主程序结束运行 ----,程序运行耗时【1009毫秒】

其他创建线程的方式

当然,除了以上四种主要的线程创建方式之外,也还有很多其他的方式可以启动多线程任务。比如通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务,关于第三方任务调度框架的例子还请查询相关资料。

你可能感兴趣的:(Java程序员进阶之路,java,jvm,面试)