哈喽,我是狗哥。话很少说,金三银四,不少同窗立刻就要参加春招了。而多线程确定是面试必问的,开篇以前,问你们一个问题:建立线程到底有几种方式?前端
相信以上答案不少同窗都能答出来。但它们都是错误的,其实建立线程的方式只有一种。为何?狗哥你丫逗我么?横看竖看,至少也得两种呀。别急,放下刀。且听我慢慢分析:java
首先是继承 Thread,建立线程最经典的方法,这种方法很常见啦。刚入门的时候,狗哥写过不知道多少遍了。它的写法是这样的:面试
public class MyThread extends Thread { @Override public void run() { System.out.println("经过集成 Thread 类实现线程"); } } // 如何使用 new MyThread().start()
如代码所示:继承 Thread 类,并重写了其中的 run () 方法,以后直接调用 start() 便可实现多线程。相信上面这种方式你必定很是熟悉,而且常常在工做中使用它们。算法
也是最经常使用的方法,写法以下:数据库
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("经过实现 Runnable 方式实现线程"); } } // 使用 // 一、建立MyRunnable实例 MyRunnable runnable = new MyRunnable(); //2.建立Thread对象 //3.将MyRunnable放入Thread实例中 Thread thread = new Thread(runnable); //4.经过线程对象操做线程(运行、中止) thread.start();
如代码所示,这种方法实际上是定义一个线程执行的任务(run 方法里面的逻辑)并无建立线程。它首先经过 MyRunnable类实现 Runnable 接口,而后重写 run () 方法,以后还要把这个实现了 run () 方法的实例传到 Thread 类中才能够实现多线程。编程
说完这两种在工做中最经常使用的,咱们再说说第三种。在 Java 中,咱们建立线程池是这样的:设计模式
// 10 是核心线程数量 ExecutorService service = Executors.newFixedThreadPool(10);
点进去 newFixedThreadPool 源码,在 IDEA 中调试,能够发现它的调用链是这样的:微信
Executors.newFixedThreadPool(10) --> new ThreadPoolExecutor(一堆参数) --> Executors.defaultThreadFactory()
能够发现最终仍是调用了 Executors.defaultThreadFactory()
方法,而这个方法的源码是这样的:数据结构
static class DefaultThreadFactory implements ThreadFactory { // 线程池序号 static final AtomicInteger poolNumber = new AtomicInteger(1); // 线程序号 final AtomicInteger threadNumber = new AtomicInteger(1); // 线程组 final ThreadGroup group; // 线程池前缀 final String namePrefix; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } /** * 重点方法 * @param r * @return */ @Override 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 () 建立线程的。因此线程池建立线程并无脱离以上的两种基本的建立方式。
第四种是有返回值的 Callable 建立线程,用法是这样的:
public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { return new Random().nextInt(); } // 使用方法 // 一、建立线程池 ExecutorService service = Executors.newFixedThreadPool(10); // 二、提交任务,并用 Future提交返回结果 Future< Integer > future = service.submit(new MyCallable()); }
Callable 与 Runnable 名字还有点像,区别在于 Runnable 是无返回值的。它们的本质都是定义线程要作的任务(call 或 run 方法里面的逻辑),而不是说他们自己就是线程。但不管有无返回值,它们都是须要被线程执行。
如代码所示,它们能够提交到线程池执行,经过 sumbit 方法提交。这时就参考方式三,由线程工厂负责建立线程。固然,还有其余方法执行 Callable 任务。可是无论怎么说,它仍是离不开实现 Runnable 接口和继承 Thread 类这两种方式。
咱们使用 Timer 的方式以下:
public class MyTimer { public static void main(String[] args) { timer(); } /** * 指定时间 time 执行 schedule(TimerTask task, Date time) */ public static void timer() { Timer timer = new Timer(); // 设定指定的时间time,此处为2000毫秒 timer.schedule(new TimerTask() { public void run() { System.out.println("执行定时任务"); } }, 2000); } }
如代码所示,Timer 定时器在两秒以后执行一些任务,它也确实建立了线程,可是深刻源码:
private final TimerThread thread = new TimerThread(queue); public Timer() { this("Timer-" + serialNumber()); } public Timer(String name) { thread.setName(name); thread.start(); } class TimerThread extends Thread { // 省略内部方法 }
注意到 TimerThread ,它仍是继承于 Thread ,因此 Timer 建立线程最后又绕回到最开始说的两种方式了。
有同窗可能说,狗哥你这扯半天不仍是两种方式么?我答对了呀。。。别急,容我喝口水,下面分析为什么说它是一种?
注意到 Thread 类中有一个 run 方法:
private Runnable target; @Override public void run() { if (target != null) { target.run(); } }
先看实现 Runnable 方式,它启动线程仍是须要调用 start 方法(由于是 Native 方法咱们看不到具体逻辑),可是线程要执行任务必须仍是要调用 run 方法(否则线程执行的是啥?)。
咱们看代码,run 方法很是简单。它判断 target 不为 null 就直接执行 target 的 run 方法。而 target 正是咱们实现的 Runnable ,使用 Runnable 接口实现线程时传给 Thread 类的对象。
在看继承 Thread 方式,它调用 thread.start(),最终调用的仍是 run 方法(run() 里面是任务)。只不过这个 run() 是咱们已经重写的 run() 而不是上面 Runnable(target) 的 run()。
看到这里可算明白了,事实上建立线程本质只有一种方式,就是构造一个 Thread 类,这是建立线程的惟一方式,不一样的只是 run 方法(执行内容)的实现方式。
一、2 两种方式它们的不一样点仅仅在于实现线程执行内容的不一样,那么运行内容来自于哪里呢?
本质上,实现线程只有一种方式,而要想实现线程执行的内容,却有两种写法:
而后把咱们想要执行的代码传入,让线程去执行。在此基础上,若是咱们还想有更多实现线程的方式,好比线程池、Callable 以及 Timer 定时器,只须要在此基础上进行封装便可。
答案是:Runnable 写法。
这个很少说,Java 是单继承。若是使用继承 Thread 的写法。将不利于后续扩展。
用 Runnable 负责定义 run() 方法(执行内容)。这种状况下,它与 Thread 实现了解耦。Thread 负责线程的启动以及相关属性设置。
在一些状况下能够提升性能。好比:线程执行的内容很简单,就是打印个日志。若是使用 Thread 实现,那它会从线程建立到销毁都要走一遍,须要屡次执行时,还须要屡次走这重复的流程,内存开销很是大。
可是咱们使用 Runnable 就不同了。能够把它扔到线程池里面,用固定的线程执行。这样,显然是能够提升效率的。
若是看到这里,喜欢这篇文章的话,请帮点个好看。微信搜索一个优秀的废人,关注后回复电子书送你 100+ 本编程电子书 ,不仅 Java 哦,详情看下图。回复1024送你一套完整的 java 视频教程。