Java笔记··By/蜜汁炒酸奶

闲聊 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
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();
    }  
}
1
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);  
	    }  
	}
	// ... 
}
1
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();  
    }  
}
1
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();  
    }  
}
1
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();
    }  
}
1
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();
    }  
}
1
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(); 
    }  
}
1
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结构图

通过结构图我们可知 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);  
        }  
//... 
}
1
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  
}
1
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);  
}
1
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;  
}
1
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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

预览

相关资源

文中提到的源代码等相关资源
Loading comments...
0 条评论

暂无数据

example
预览