闲聊 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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
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
3
4
5
6
7
8
9
10
11
12
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
3
4
5
6
7
8
9
10
11
12
13
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
4
5
6
7
8
9
10
11
12
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();
}
}
2
3
4
5
6
7
8
9
10
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Future
或者说 FutureTask
需要轮询 isDone()
判断是否完成,或者直接调用 get()
方法获取结果,而在调用get()
方法时会阻塞主线程,直到获取到结果为止。
Callable
和 Runnable
类似, Runnable
中的简化方式很多情况下同样适用于 Callable
。但是 Thread
类的构造函数只有基于 Runnable
的,并没有 Callable
,为了 Thread
能执行 Callable
,于是有了 FutureTask
。
通过结构图我们可知 FutureTask
实现了 RunnableFuture
接口,RunnableFuture
接口继承了 Runnable
和 Future
接口,所以 FutureTask
既可以执行 Callable
也可以执行 Runnable
。
FutureTask
类本身是适配器模式中的一个适配器( Adapter ),Runnable
是 目标接口 Target
, Callable
是与目标接口 Runnable
不兼容的接口 ( Adaptee ,本身也可以是个类)。
同样 FutureTask
运行 Runnable
的话也会涉及另一个适配器 RunnableAdapter
,这是个将 Runnable
转为 Callable
的适配器。通过这两个适配器,很容易让人想到 IT 世界的一句俗语:没有什么问题是加一层不能解决的。
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);
}
//...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
由此可知,当通过 FutureTask
执行 Runnable
时, 会将 Runnable
转化为 Callable
对象,在构造函数中也证实了这一点。
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
2
3
4
在 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);
}
2
3
4
5
而在 RunnableAdapter
类 中可见转化的 Callable
对象的call
方法调用的还是 Runnable
的 run
方法。
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
2
3
4
5
6
7
8
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);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CompletableFuture
本身有众多方法,由于不是本篇重点,不再过多展开。
除特别注明外,本站所有文章均为 windcoder 原创,转载请注明出处来自: xianliaojava-xianchengchuangjian

暂无数据