一、线程的创建十种方式
一个经典的面试八股问题是:java 中有几种线程的创建方式?
一般的回答是:
- 实现Thread类。
- 实现Runable接口。
- 实现Callable接口。
也可以加上一个利用ExecutorService线程池:
- 实现Thread类。
- 实现Runable接口。
- 实现Callable接口。
- 使用
ExecutorService
线程池
这是这个八股文的标准答案!
JDK8中又添加了个CompletableFuture来实现线程,还可以使用线程组ThreadGroup、FutureTask来实现。
- 实现Thread类。
- 实现Runable接口。
- 实现Callable接口。
- 使用
ExecutorService
线程池 - 使用
CompletableFuture
类 - 基于
ThreadGroup
线程组 - 使用
FutureTask
类
还有其他的方式吗?当然有还有 Timer、ForkJoin
线程池 、Stream
并行流。
- 实现Thread类。
- 实现Runable接口。
- 实现Callable接口。
- 使用
ExecutorService
线程池 - 使用
CompletableFuture
类 - 基于
ThreadGroup
线程组 - 使用
FutureTask
类 - 使用Timer定时类
- 使用ForkJoin线程池
- Stream并行流
具体实现代码如下:
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.*;
/**
* @author @四维穿梭
*/
public class ThreadCreate {
/**
* 创建线程的方式一:继承Thread类
*/
static class ByThread extends Thread {
private String name;
public ByThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + Thread.currentThread().getName());
}
}
/**
* 创建方式二:实现Runnable接口
*/
static class ByRunnable implements Runnable {
private String name;
public ByRunnable(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + Thread.currentThread().getName());
}
}
/**
* 创建方式三:实现Callable接口
*/
static class ByCallable implements Callable<String>
{
private String name;
public ByCallable(String name){
this.name = name;
}
@Override
public String call() throws Exception {
System.out.println(name + Thread.currentThread().getName());
return name;
}
}
/**
* 4 通过线程池创建线程
*/
public static void createByPool(){
/**
* AbortPolicy:这是默认的策略。当线程池无法接受新任务时,
* 会抛出RejectedExecutionException异常并中止任务的执行。调用者需要处理这个异常。
* DiscardPolicy:这种策略会静默丢弃任务,不会抛出任何异常,也不会做其他处理。
* DiscardOldestPolicy:此策略会丢弃队列中最旧的任务,以便为新被拒绝的任务腾出空间。
* 被丢弃的任务不会被执行,而且也不会有任何通知。
* CallerRunsPolicy:在这种策略下,如果线程池不能再接收新任务,
* 那么提交任务的调用线程将自行执行该任务。
*/
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,3,0, TimeUnit.SECONDS
, new LinkedBlockingDeque<Runnable>(3), Executors.defaultThreadFactory()
,new ThreadPoolExecutor.CallerRunsPolicy());
poolExecutor.submit(()->{
System.out.println("4: created by poolExecutor " + Thread.currentThread().getName());
});
poolExecutor.shutdown();
}
/**
* 5 create by CompletableFuture
*/
public static void createByCompletableFuture(){
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println("5: created by completableFuture " + Thread.currentThread().getName());
return "hello CompletableFuture";
});
completableFuture.thenAccept(System.out::println);
}
/**
* 6 created by ThreadGroup
*/
public static void createByThreadGroup(){
ThreadGroup threadGroup = new ThreadGroup("groupName");
new Thread(threadGroup,()->{
System.out.println("6: created by threadGroup " + Thread.currentThread().getName());
}, "create by threadGroup T1").start();
new Thread(threadGroup,()->{
System.out.println("6: created by threadGroup " + Thread.currentThread().getName());
}, "create by threadGroup T2").start();
}
/**
* 7 create by FutureTask
*/
public static void createByFutureTask(){
FutureTask<String> futureTask = new FutureTask<>(()-> {
System.out.println("7: created by futureTask " + Thread.currentThread().getName());
return "hello FutureTask";
});
new Thread(futureTask).start();
}
/**
* 8 created by Timer
*/
public static void createByTimer() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("8: created by Timer " + Thread.currentThread().getName());
}
}, 0, 1000);
}
/**
* 9 create by ForkJoinPool
*/
public static void createByForkJoin(){
ForkJoinPool join = new ForkJoinPool();
join.execute(()->{
System.out.println("9: created by ForkJoinPool " + Thread.currentThread().getName());
});
}
/**
* 10 create by parallelStream
*/
public static void createByParallelStream(){
List<String> list = Arrays.asList("10A", "10B");
list.parallelStream().forEach(System.out::println);
}
/**
*
* @param args
*/
public static void main(String[] args) {
try {
ByThread byThread1 = new ByThread("1: byThread ");
byThread1.start();
byThread1.join();
Thread byRunnable = new Thread(new ByRunnable("2: byRunnable "));
byRunnable.start();
byRunnable.join();
FutureTask<String> futureTask = new FutureTask<>(new ByCallable("3: byCallable "));
Thread byCallable = new Thread(futureTask);
byCallable.start();
byCallable.join();
createByPool();
createByCompletableFuture();
createByThreadGroup();
createByFutureTask();
createByTimer();
createByForkJoin();
createByParallelStream();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
1: byThread Thread-0
2: byRunnable Thread-1
3: byCallable Thread-2
4: created by poolExecutor pool-1-thread-1
5: created by completableFuture ForkJoinPool.commonPool-worker-1
hello CompletableFuture
6: created by threadGroup create by threadGroup T1
6: created by threadGroup create by threadGroup T2
7: created by futureTask Thread-3
8: created by Timer Timer-0
9: created by ForkJoinPool ForkJoinPool-1-worker-1
10B
10A
8: created by Timer Timer-0
8: created by Timer Timer-0
8: created by Timer Timer-0
8: created by Timer Timer-0
二、八股文中隐藏的问题
“继承Thread
类、实现Runnable
接口、实现Callable
接口”,这应该是广为人知的答案,不管是刚入行的小白,还是在业内深耕已久的老鸟,相信都背过这一道八股文。
我们看一下具体实现代码:
/**
* 创建线程的方式一:继承Thread类
*/
static class ByThread extends Thread {
private String name;
public ByThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + Thread.currentThread().getName());
}
}
/**
* 创建方式二:实现Runnable接口
*/
static class ByRunnable implements Runnable {
private String name;
public ByRunnable(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + Thread.currentThread().getName());
}
}
/**
* 创建方式三:实现Callable接口
*/
static class ByCallable implements Callable<String>
{
private String name;
public ByCallable(String name){
this.name = name;
}
@Override
public String call() throws Exception {
System.out.println(name + Thread.currentThread().getName());
return name;
}
}
public static void main(String[] args) {
try {
ByThread byThread1 = new ByThread("1: byThread ");
byThread1.start();
Thread byRunnable = new Thread(new ByRunnable("2: byRunnable "));
byRunnable.start();
FutureTask<String> futureTask = new FutureTask<>(new ByCallable("3: byCallable "));
Thread byCallable = new Thread(futureTask);
byCallable.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
这三种方式,都是通过Thread.start()的启动线程。通过Runalble接口实现,先new
出Runnable
对象,接着再new
一个Thread
对象,然后把Runnable
丢给Thread
,接着调用start()
方法,此时才能真正意义上创建一条线程。通过Callable接口也类似,都是通过Thread类来实现线程创建的。
三种方式中,只有继承Thread
类,才能真正创建一条线程。因为当你用一个类,继承Thread
类时,它内部所有的方法,都会被继承过来,所以当前类可以直接调用start()
方法启动,更具体点来说,在Java
中,创建线程的方式就只有一种:调用Thread.start()
方法!只有这种形式,才能在真正意义上创建一条线程!
而例如ExecutorService
线程池、ForkJoin
线程池、CompletableFuture
类、Timer
定时器类、parallelStream
并行流……,如果有去看过它们源码的小伙伴应该清楚,它们最终都依赖于Thread.start()
方法创建线程。
好了,搞清楚这点之后,再回头来看Runnable、Callable
,这俩既然不是创建线程的方式,那它们究竟是什么?这点咱们放到后面去讨论,先来聊聊“Java
有三种创建线程的方式”,这个以讹传讹的八股文,到底是怎么来的呢?
究根结底,这个错误观念的源头,来自于《Java
编程思想》(《Thinking In Java
》)和《Java
核心技术》(《Core Java
》)这两本书。在《Core Java
》这本书的第12、13
章,专门对多线程编程进行了讲解,提到了四种创建线程的方式:
-
• ①继承
Thread
类,并重写run()
方法; -
• ②实现
Runnable
接口,并传递给Thread
构造器; -
• ③实现
Callable
接口,创建有返回值的线程; -
• ④使用
Executor
框架创建线程池。
同样的内容,在《Thinking In Java
》的第二十一章,也有重复提及到。
三、线程和线程体
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可以与同属一个进程的其他的线程共享进程所拥有的全部资源。
线程体(Thread Body)则是线程运行的实际代码部分。当我们创建一个新的线程时,我们需要指定线程的入口点,也就是线程体。线程体可以是一个函数或者是一个已经被实例化的对象。线程一旦启动,操作系统就会运行线程体中的代码。
例如,在Java中你可以通过实现 Runnable 接口来创建一个新的线程体。Runnable 接口只有一个方法:run(),在该方法内部就是线程体。
总结起来,线程是载体,线程体是内容。
大家对Java
中创建线程只有Thread.start()
这一种方式有了更多的理解吧!而最开始给出的其他方式,要么是在封装Thread.start()
,要么是在创建线程体,而这个所谓的线程体,更接地气的说,应该是“多线程任务”。
new Runnable(...);
new Callable(...);
这并不是在创建线程,而是创建了两个可以提供给线程执行的“多线程任务”。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
当new``Thread
对象并传入一个任务时,内部会调用init()
方法,把传入的任务target
传进去,同时还会给线程起个默认名字,即Thread-x
,这个x
会从0
开始(线程名字也可以自定义)。init()
方法做一系列准备工作,如安全检测、设定名称、绑定线程组、设置守护线程……,当init()
方法执行完成后,就可以调用Thread.start()
方法启动线程啦。
四、总结
现在,再次开头的问题是不是有了更出彩的答案呢?
Java
创建线程有很多种方式啊,像实现Runnable、Callable
接口、继承Thread
类、创建线程池等等,不过这些方式并没有真正创建出线程,严格来说,Java
就只有一种方式可以创建线程,那就是通过new Thread().start()
创建。
而所谓的Runnable、Callable……
对象,这仅仅只是线程体,也就是提供给线程执行的任务,并不属于真正的Java
线程,它们的执行,最终还是需要依赖于new Thread()
……