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

闲聊 Java 线程创建

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

在 Java 中创建线程的方式有多种,但线程执行都需要通过调用 Threadstart() 方法,之后由程序判断在合适的时候执行线程中的 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 引入了 CallableFuture 以及相关类。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()方法时会阻塞主线程,直到获取到结果为止。

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

FutureTask结构图

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

FutureTask类本身是适配器模式中的一个适配器( Adapter ),Runnable 是 目标接口 TargetCallable 是与目标接口 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 方法调用的还是 Runnablerun 方法。

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
预览