创建多线程

122 阅读5分钟

一、创建线程的三种方法

  • 继承Thread,重写run()方法

    步骤:

    1. 创建一个继承Thread的类,重写run方法
    2. 创建该类的对象,调用start方法,启动新线程执行run方法

    代码示例:

    **
     * 继承Thread,重写run方法,实现创建线程
     */
    public class Demo1 extends Thread{
    ​
        @Override
        public void run() {
            System.out.println("new Thread");
        }
    ​
        public static void main(String[] args) {
            Demo1 demo1 = new Demo1();
            demo1.setName("demo1");
            demo1.start();
        }
    }
    

image-20230620192648405

优点:简单易用,适合简单的线程任务。

缺点:由于Java只支持单继承,因此如果已经继承了其他类,则无法使用该方式创建线程。

  • 实现Runnable接口,重写run()

    步骤:

    1. 创建一个Runnable接口的实现类
    2. 在实现类中重写run方法
    3. 创建该实现类的对象
    4. 创建Thread类对象,构造方法中传入实现类对象
    5. 调用Thread类的start方法,启动新线程执行run方法

    代码示例:

/**
 * 实现Runnable接口,重写run方法创建线程:
 */
public class Demo2 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "is running");
    }
​
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Demo2());
        thread1.setName("Runnable Thread");
        thread1.start();
​
        // Lambda表达式创建
        new Thread(new Runnable() {
            @Override
            public void run() {
                // todo 执行业务
​
                System.out.println("Lambda表达式 Runnable创建线程--2");
            }
        }).start();
    }
}

image-20230620193403862

优点:可以避免单继承的限制,适合多个线程共享一个资源的情况。

缺点:需要额外的类来实现Runnable接口,编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法

  • 通过Callable和Future接口创建线程

    步骤:

    1. 创建一下实现Callable接口的实现类,重写call方法
    2. 创建该实现类的对象,将其提供给FutureTask对象的构造参数
    3. 将FutureTask对象提供给Thread的构造函数以创建Thread对象
    4. 调用thread的start方法,启动子线程只执行call方法
    5. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    代码示例:

    /**
     * 通过Callable和Future接口创建线程
     */
    public class Demo3 implements Callable<String> {
        @Override
        public String call() throws Exception {
            // todo 执行业务
            return "Callable创建线程--1";
        }
    ​
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            Demo3 demo3 = new Demo3();
            FutureTask<String> futureTask = new FutureTask(demo3);
            new Thread(futureTask).start();
            System.out.println(futureTask.get());
    ​
            // Lambda表达式创建
            FutureTask<String> futureTask2 = new FutureTask(() -> {
                // todo 执行业务
    ​
                return "Lambda表达式 Callable创建线程--2";
            });
            
            new Thread(futureTask2).start();
            System.out.println(futureTask2.get());
        }
    }
    

image-20230620192314318

优点:可以返回线程执行的结果,适合需要获取线程执行结果的情况。

缺点:相比前两种方式,稍微复杂一些。

二、Callable与Runnable区别

  • 为了实现Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法。
  • call()方法允许异常向上抛出或者内部抓取,而run()的异常则要求内部处理
  • 为实现Callable而必须重写call方法
  • Callable不能直接替换Runnable,因为Thread类的构造方法根本没有Callable

三、Future接口

Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。

拥有方法:

  • public boolean cancel(boolean mayInterrupt):用于停止任务。如果尚未启动,它将停止任务。如果已启动,则仅在mayInterrupt为true时才会中断任务。
  • public Object get()抛出InterruptedException,ExecutionException:用于获取任务的结果。如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
  • public boolean isDone():如果任务完成,则返回true,否则返回false

四、FutureTask

Java库具有具体的FutureTask类型,该类实现了Runnable和Future,并方便地将两种功能组合在一起。 可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread对象。

作用:实现Callable接口创建线程的方式,当call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。因此,可以使用FutureTask对象。

一般使用:

在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成

• 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态

一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

• 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法

• 一旦计算完成,就不能再重新开始或取消计算

• get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常

• get只计算一次,因此get方法放到最后,可以设置超时时间

五、总结

总的来说就三种创建方式

  1. 继承Thread并重写run()方法
  2. 创建Thread并执行Runnable任务
  3. 创建Thread并执行Callable任务

使用推荐:

  • 单继承用第一种
  • 如果已经继承了其他类而又不需要返回值,就用第二种
  • 需要返回值,用第三种