实现线程的方式到底有几种?大部分人会说有 2 种、3 种或是 4 种,很少有人会说有 1 种。我们看看具体指什么?
1. 实现 Runnable 接口
public class RunnableThread implements Runnable {@Overridepublic void run() { System.out.println('用实现Runnable接口实现线程'); }}
2. 继承 Thread 类
public class ExtendsThread extends Thread {@Overridepublic void run() { System.out.println('用Thread类实现线程'); }}
与第 1 种方式不同的是它没有实现接口,而是继承 Thread 类,并重写了其中的 run() 方法。
3. 线程池创建线程
static class DefaultThreadFactory implements ThreadFactory { DefaultThreadFactory() { SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() +"-thread-"; }public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(),0);if (t.isDaemon()) t.setDaemon(false);if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY);return t; }
对于线程池而言,本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFactory ,它会给线程池创建的线程设置一些默认值,比如:线程的名字、是否是守护线程,以及线程的优先级等。
但是无论怎么设置这些属性,最终它还是通过 new Thread() 创建线程的 ,只不过这里的构造函数传入的参数要多一些。
由此可以看出通过线程池创建线程并没有脱离最开始的那两种基本的创建方式,因为本质上还是通过 new Thread() 实现的。
面试加分回答:
描述完前两种方式,进一步引申说“我还知道线程池和 Callable 也是可以创建线程的,但是它们本质上也是通过前两种基本方式实现的线程创建。”
4. 有返回值的 Callable 创建线程
class CallableTask implements Callable<Integer> {@Overridepublic Integer call() throws Exception {return new Random().nextInt(); }}//创建线程池ExecutorService service = Executors.newFixedThreadPool(10);//提交任务,并用 Future提交返回结果Future<Integer> future = service.submit(new CallableTask());
Runnable 创建线程是无返回值的,而 Callable 和与之相关的 Future、FutureTask,它们可以把线程执行的结果作为返回值返回,如代码所示,实现了 Callable 接口,并且给它的泛型设置成 Integer,然后它会返回一个随机数。
但是,无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,是需要被执行的,而不是说它们本身就是线程。
它们可以放到线程池中执行,如代码所示, submit() 方法把任务放到线程池中,并由线程池创建线程,不管用什么方法,最终都是靠线程来执行的,而子线程的创建方式仍脱离不了最开始讲的两种基本方式,也就是实现 Runnable 接口和继承 Thread 类。
5. 其他创建方式
class TimerThread extends Thread {//具体实现}
定时器也可以实现线程,如果新建一个 Timer,令其每隔 10 秒或设置两个小时之后,执行一些任务,那么这时它确实也创建了线程并执行了任务。
但如果我们深入分析定时器的源码会发现,本质上它还是会有一个继承自 Thread 类的 TimerThread,所以定时器创建线程最后又绕回到最开始说的两种方式。
或许你还会说,我还知道一些其他方式,比如匿名内部类或 lambda 表达式方式。
/** *描述:匿名内部类创建线程 */new Thread(new Runnable() { @Overridepublic void run() { System.out.println(Thread.currentThread().getName()); }}).start();}}
实际上,匿名内部类或 lambda 表达式创建线程,它们仅仅是在语法层面上实现了线程,并不能把它归结于实现多线程的方式,如匿名内部类实现线程的代码所示,它仅仅是用一个匿名内部类把需要传入的 Runnable 给实例出来。
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();}
再来看 lambda 表达式方式,最终它们依然符合最开始所说的那两种实现线程的方式。
到这里,我们先认为有两种创建线程的方式,而其他的创建方式,比如线程池或是定时器,它们仅仅是在 new Thread() 外做了一层封装,如果我们把这些都叫作一种新的方式,那么创建线程的方式便会千变万化、层出不穷,比如 JDK 更新了,它可能会多出几个类,会把 new Thread() 重新封装,表面上看又会是一种新的实现线程的方式,透过现象看本质,打开封装后,会发现它们最终都是基于 Runnable 接口或继承 Thread 类实现的。
-----------
本文整理自《Java并发编程78讲》技术专栏
勾哥:徐大说到现在,我们能理解所有实现线程的方式归根结底就是基于 Runnable 接口或继承 Thread 类,那为什么徐大一开始说
这两种方式本质上是一种呢?
关注公号
勾勾的Java宇宙,下期告诉你。