Java 有三种方式实现多线程,继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。还有匿名内部类方式,Lambda 表达式方式简化开发。

Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会newThread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。

1、Thread

Thread 创建线程方式:创建线程类

  • start() 方法底层其实是给 CPU 注册当前线程,并且触发 run() 方法执行

  • 线程的启动必须调用 start() 方法,如果线程直接调用 run() 方法,相当于变成了普通类的执行,此时主线程将只有执行该线程

  • 建议线程先创建子线程,主线程的任务放在之后,否则主线程(main)永远是先执行完

Thread 构造器:

  • public Thread()

  • public Thread(String name)

public class p1 {
    public static void main(String[] args) {
        // 创建线程类方式
        Thread t1 = new MyThread();
        t1.start();
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0 ; i < 100 ; i++ ) {
            // 子线程输出
            System.out.println(Thread.currentThread().getName() + "->" + i);
        }
    }
}

继承 Thread 类的优缺点:

  • 优点:编码简单

  • 缺点:线程类已经继承了 Thread 类无法继承其他类了,功能不能通过继承拓展(单继承的局限性)

2、Runnable

Runnable 创建线程方式:创建线程类

Thread 的构造器:

  • public Thread(Runnable target)

  • public Thread(Runnable target, String name)

public class p2 {
    public static void main(String[] args) {
        Runnable target = new MyRunnable();
        Thread t1 = new Thread(target,"Runnable线程");
		t1.start();
        Thread t2 = new Thread(target);//Thread-0
    }
}

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName() + "->" + i);
        }
    }
}

Thread 类本身也是实现了 Runnable 接口,Thread 类中持有 Runnable 的属性,执行线程 run 方法底层是调用 Runnable#run:

 public class Thread implements Runnable {
     private Runnable target;
     
     public void run() {
         if (target != null) {
             // 底层调用的是 Runnable 的 run 方法
             target.run();
         }
     }
 }

Runnable 方式的优缺点:

  • 缺点:代码复杂一点。

  • 优点

    • 线程任务类只是实现了 Runnable 接口,可以继续继承其他类,避免了单继承的局限性

    • 同一个线程任务对象可以被包装成多个线程对象

    • 适合多个多个线程去共享同一个资源

    • 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立

    • 线程池可以放入实现 Runnable 或 Callable 线程任务对象

3、Callable

实现 Callable 接口:

  1. 定义一个线程任务类实现 Callable 接口,申明线程执行的结果类型

  2. 重写线程任务类的 call 方法,这个方法可以直接返回执行的结果

  3. 创建一个 Callable 的线程任务对象

  4. 把 Callable 的线程任务对象包装成一个未来任务对象

  5. 把未来任务对象包装成线程对象

  6. 调用线程的 start() 方法启动线程

public FutureTask(Callable<V> callable):未来任务对象,在线程执行完后得到线程的执行结果

  • FutureTask 就是 Runnable 对象,因为 Thread 类只能执行 Runnable 实例的任务对象,所以把 Callable 包装成未来任务对象

public V get():同步等待 task 执行完毕的结果,如果在线程中获取另一个线程执行结果,会阻塞等待,用于线程同步

  • get() 线程会阻塞等待任务执行完成

  • run() 执行完后会把结果设置到 FutureTask 的一个成员变量,get() 线程可以获取到该变量的值

public class p3 {
    public static void main(String[] args) {
        Callable<String> call = new MyCallable();
        FutureTask<String> task = new FutureTask<>(call);
        Thread t1 = new Thread(task);
        t1.start();
        try {
            // 获取call方法返回的结果(正常/异常结果)
            String s = task.get(); 
            System.out.println(s);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName() + "->" + "MyCallable String";
    }
}

优缺点:

  • 优点:同 Runnable,并且能得到线程执行的结果

  • 缺点:编码复杂

4、匿名内部类方式、Lambda 表达式方式
public class p1 {
    public static void main(String[] args) {
        // 匿名内部类方式
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t1 线程执行中");
            }
        });
        t1.start();

        // Lambda 表达式创建匿名内部类的线程对象
        Thread t2= new Thread(() -> {
            System.out.println("t2 线程执行中");
        });
        t2.start();
    }
}    

使用匿名内部类的方式可以方便地定义并实例化线程对象,并实现线程的执行逻辑。它对于一些简单的线程任务可以简洁地表达,但对于复杂的线程逻辑,建议使用具名的内部类或者单独定义一个类来实现Runnable接口

使用 lambda 表达式代替了匿名内部类,使得代码更加简洁。适用于只包含一个抽象方法的接口,例如 Runnable 接口和 Callable 接口。对于其他接口,如果包含多个抽象方法,就无法使用 lambda 表达式来创建匿名内部类。

总结:
  Java创建线程有很多种方式啊,像实现Runnable、Callable接口、继承Thread类、创建线程池等等,不过这些方式并没有真正创建出线程,严格来说,Java就只有一种方式可以创建线程,那就是通过 new Thread().start() 创建。
      而所谓的Runnable、Callable……对象,这仅仅只是线程体,也就是提供给线程执行的任务,并不属于真正的Java线程,它们的执行,最终还是需要依赖于new Thread()

借鉴:https://github.com/Seazean/JavaNote/blob/main/Prog.md

大家都说Java有三种创建线程的方式!并发编程中的惊天骗局!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐