闲聊 Java 线程创建

闲聊 Java 线程创建

Green Thread 早已被废弃,我们不再讨论,本篇基于 OpenJava21 从线程创建开始看下 Java 中的线程。

在 Java 中创建线程的方式有多种,但线程执行都需要通过调用 Thread 的 start() 方法,之后由程序判断在合适的时候执行线程中的 run 方法。

线程创建方法汇总图

1 继承 Thread 类

1.1 基础实现

最基础的创建方式之一。先创建一个新类,继承 Thread 类,重写 run 方法,之后在 main 方法等需要的地方实例化并调用其 start() 方法即可。

public class MThread extends Thread{
@Override
public void run() {
System.out.println("这是一个继承 Thread,重写 run 方法");
}
}
public class MThreadTest {
public static void main(String[] args) {
MThread t = new MThread();
t.start();
}
}

1.2 基于匿名内部类

对于临时的,也可以通过匿名内部类简化,本质是创建了一个继承了 Thread 类的匿名类,并重写了 run 方法。

public class MThreadTest {
public static void main(String[] args) {
// 匿名内部类
Thread t2 = new Thread(){
@Override
public void run() {
System.out.println("这是一个继承 Thread 的匿名内部类,重写 run 方法");
}
};
t2.start();
}
}

Thread 类本身是一个实现了 Runnable 的类。

public class Thread implements Runnable {
// ....
@Override
public void run() {
Runnable task = holder.task;
if (task != null) {
Object bindings = scopedValueBindings();
runWith(bindings, task);
}
}
// ...
}

2 实现 Runnable 接口

2.1 基础实现

最基础的创建方式之一。先创建一个实现 Runnable 接口的类,在该类中实现 run 方法。之后在调用类中先创建一个该实现类实例如 r ,之后将其传入 Thread 类的构造方法中创建一个 Thread 实例,最后调用 start() 方法即可。

public class MThread implements Runnable{
@Override
public void run() {
System.out.println("这是一个继承 Runnable ,重写 run 方法");
}
}
public class MThreadTest {
public static void main(String[] args) {
MThread r = new MThread();
Thread t = new Thread(r);
t.start();
}
}

2.2 基于匿名内部类

同样可以通过匿名内部类简化

public class MThreadTest {
public static void main(String[] args) {
// 基于匿名内部类
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("这是一个实现 Runnable 的匿名内部类,重写 run 方法");
}
});
t2.start();
}
}

2.3 基于 Lambda 表达式

在后来的发展中,Java8 引入了 Lambda 表达式,所以我们也可以基于 Lambda 表达式继续简化创建方式。

public class MThreadTest {
public static void main(String[] args) {
// 使用 Lambda 表达式代替匿名内部类
Thread t3 = new Thread(()->{
System.out.println("这是一个使用 Lambda 表达式实现 Runnable 的匿名内部类,重写 run 方法");
});
t3.start();
}
}

Lambda 表达式本质上只是一种”语法糖“,简化了一些编写方式。

2.4 工厂方式 ofPlatform

在引入虚拟线程后,又有了通过工厂方式创建平台线程的方式

public class MThread implements Runnable{
@Override
public void run() {
System.out.println("这是一个继承 Runnable ,重写 run 方法");
}
}
public class MThreadTest {
public static void main(String[] args) {
Thread t4 = Thread.ofPlatform().unstarted(r);
t4.setName("平台线程");
t4.start();
}
}

3 可获取结果的异步编程

3.1 使用 Callable 和 Future

前两种方式是无法拿到线程的执行结果的,想要执行完线程后返回相关结果,就需要使用这第三种方式。Java 1.5 引入了 Callable 和 Future 以及相关类。Callable 是一个具有类型参数泛型接口,它的 call 方法可以返回一个结果。Future 是一个表示异步计算的结果的接口,是一个抽象表示,它提供了检查计算是否完成 isDone() 、取消计算 cancel 和获取计算结果 get 等方法,但本身没有提供任务的执行的能力。想要获得 Future 实例,通常需要借助 FutureTask 或者 Executor 线程池框架。

public class MThread implements Callable<String> {
@Override
public String call() throws Exception {
return "这是一个实现 Callable 的方式,执行 call 方法"+Thread.currentThread()+"-"+Thread.currentThread().isDaemon();
}
}
public class MThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> f = new FutureTask<String>(new MThread());
Thread t = new Thread(f);
t.start();
System.out.println("main 获取 f 的结果:"+f.get());
// Lambda
FutureTask<String> f2 = new FutureTask<String>(()->{
return "这是一个使用 Lambda 表达式实现 Callable 的的匿名内部类";
});
Thread t2 = new Thread(f2);
t2.start();
System.out.println("main 获取 f2 的结果:"+f2.get());
// 线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MThread());
System.out.println("这是一个基于线程池运行的 Future:"+future.get());
executor.shutdown();
}
}

Future 或者说 FutureTask 需要轮询 isDone() 判断是否完成,或者直接调用 get() 方法获取结果,而在调用get()方法时会阻塞主线程,直到获取到结果为止。

Callable 和 Runnable 类似, Runnable 中的简化方式很多情况下同样适用于 Callable 。但是 Thread 类的构造函数只有基于 Runnable 的,并没有 Callable,为了 Thread 能执行 Callable ,于是有了 FutureTask 。

FutureTask结构图

通过结构图我们可知 FutureTask 实现了 RunnableFuture 接口,RunnableFuture 接口继承了 Runnable 和 Future 接口,所以 FutureTask 既可以执行 Callable 也可以执行 Runnable 。

FutureTask类本身是适配器模式中的一个适配器( Adapter ),Runnable 是 目标接口 Target, Callable 是与目标接口 Runnable 不兼容的接口 ( Adaptee ,本身也可以是个类)。

同样 FutureTask 运行 Runnable 的话也会涉及另一个适配器 RunnableAdapter ,这是个将 Runnable 转为 Callable 的适配器。通过这两个适配器,很容易让人想到 IT 世界的一句俗语:没有什么问题是加一层不能解决的。

FutureTask 与 Runnable 对比图

Thread 在执行 FutureTask 时将其当作了一个 Runnable ,所以会调用其 run 方法,通过源码中 result = c.call(); 可知调用的是 Callable 对象的call 方法。

public void run() {
// ...
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
//...
}

由此可知,当通过 FutureTask 执行 Runnable 时, 会将 Runnable 转化为 Callable 对象,在构造函数中也证实了这一点。

public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}

在 Executors.callable(runnable, result); 中通过 RunnableAdapter 类做的转化。

public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}

而在 RunnableAdapter 类 中可见转化的 Callable 对象的call 方法调用的还是 Runnable 的 run 方法。

RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}

3.2 使用 CompletableFuture

CompletableFuture 是在JDK1.8提供的一种更加强大的异步编程API,它既实现了 Future 接口,拥有 Future 的功能特性,又实现了 CompletionStage 接口,CompletionStage 接口定义了任务编排的方法,在执行完一个阶段后可以通过thenApply 等方法继续执行下一个阶段任务,从而避免了需要先通过get()阻塞主线程获得结果再进行后续操作。CompletableFuture默认使用的线程池是 ForkJoinPool.commonPool(), 它创建的线程都是守护线程,具体的在线程池部分统一介绍。

//supplyAsync 内部默认使用ForkJoinPool线程池执行任务,可通过类似 supplyAsync(Supplier<U> supplier,Executor executor) 指定自己的线程池
CompletableFuture<String> completableFuture=CompletableFuture.supplyAsync(()->{
//模拟执行耗时任务
System.out.println("这是一个基于 CompletableFuture 和默认线程池的实例:" + Thread.currentThread().isDaemon());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回结果
return "100";
}).whenComplete((v,r)->{
System.out.println("计算结果是: "+v);
});
//默认使用的线程池里的线程是 daemon 的。main线程结束后,整个程序也结束了
// 这里通过 join() 用于等待 completableFuture 线程终止
completableFuture.join();
// 类似也可以将main线程join 保证任务里的代码执行完-仅限测试
// Thread.currentThread().join(10000);

CompletableFuture 本身有众多方法,由于不是本篇重点,不再过多展开。

本文采用 CC BY-NC-SA 4.0 许可,转载请注明出处与作者。

支持一下

如果这篇文章对你有帮助,欢迎打赏或分享。

打赏支持

评论