叮铃铃~java
谁啊,大早上的扰人清梦,不知道好不容易有个周末吗?接电话知道是朋友约我出去钓鱼,顺便跟我聊一下前段时间让他帮忙内推一下我小侄子去实习的事情面试
见面以后,他直接开门见山,小侄子在面试的时候表现不错,最后一面是他来面的,问的至关深刻,侄子表现也不错,可是在多线程这个地方,他稍微问的深刻了一些,小朋友明显的慌张起来,不少知识点回答的至关很差(我说这小子怎么面试回来以后都不敢找我问问题了),朋友说问题不大,这里他不会卡他,过几天应该就能收到offer了服务器
虽然朋友这样说,可是内心却记下了,多线程这一块该给这小子补习一下了网络
我的公众号:Java架构师联盟,每日更新技术好文呢多线程
其实多线程应该是如今不少朋友的难点吧,没什么时间看源码,工做的时候又不多能应用获得,可是在面试的时候,这一块又是面试的重点,那应该怎么办呢?其实不须要慌张,由于面试也算是考试,考试就会划重点,我总共总结出如下几点,你们能够先看一下能详细的回想起来多少架构
线程sleep并发
线程yielddom
设置线程的优先级获取线程IDide
获取当前线程函数
设置线程上下文类加载器
线程interrupt
线程join
如何关闭—个线程
若是你能回答出来80%以上,那基本就没什么问题了,否则,你可能要仔细的往下看,有的朋友会说我还年轻,不须要这个,那我要告诉你,这些知识点确实可能不会阻挡你拿offer,可是,会影响你后期的发展速度以及在公司享受的资源,不信就去问你老大
好了,话很少说,咱们来看一下从源码角度,这些问题你都该回答那些东西
线程 sleep
sleep是一个静态方法,其有两个重载方法,其中一个须要传入毫秒数,另一个既需 要毫秒数也须要纳秒数。
sleep方法介绍
public static void sleep(long millis) throws InterruptedException public static void sleep(long millis, int nanos) throws InterruptedException
sleep方法会使当前线程进入指定毫秒数的休眠,暂停执行,虽然给定了一个休眠的 时间,可是最终要以系统的定时器和调度器的精度为准,休眠有一个很是重要的特性, 那就是其不会放弃monitor锁的全部权(在后文中讲解线程同步和锁的时候会重点介绍 monitor),下面咱们来看一个简单的例子:
package com.wangwenjun,concurrent,chapter03; public class Threadsleep public static void main(String[] args) new Thread(()-> long startTime = System.currentTimeMillis(); sleep(2_000L); long endTime = System.currentTimeMillis(); System.out.printin(String.format("Total spend %d ms", (endTime - startTime))); }).start(); long startTime = System.currentTimeMillis(); sleep(3_000L); long endTime = System.currentTimeMillis(); System.out.printin(String.format("Main thread total spend %d ms", (endTime - startTime))); } private static void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { } } }
在上面的例子中,咱们分别在自定义的线程和主线程中进行了休眠,每一个线程的休眠 互不影响,Thread.sleep只会致使当前线程进入指定时间的休眠。
使用 TimeUnit 替代 Thread.sleep
在JDK1.5之后,JDK引入了一个枚举TimeUnit,其对sleep方法提供了很好的封装, 使用它能够省去时间单位的换算步骤,好比线程想休眠3小时24分17秒88毫秒,使用 TimeUnit来实现就很是的简便优雅了 :
Thread.sleep(12257088L); TimeUnit.HOURS.sleep(3); TimeUnit・ MINUTES.sleep(24); TimeUnit, SECONDS・ sleep(17); TimeUnit.MILLISECONDS.sleep(88);
一样的时间表达,TimeUnit显然清晰不少,笔者强烈建议,在使用Thread.sleep的地 方,彻底使用TimeUnit来代替,由于sleep能作的事,TimeUnit所有都能完成,而且功能 更加的强大,在本书后面的内容中,我将所有采用TimeUnit替代sleep。
线程 yield
yield方法介绍
yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源,若是 CPU的资源不紧张,则会忽略这种提醒。
调用yield方法会使当前线程从RUNNING状态切换到RUNNABLE状态,通常这个方 法不太经常使用:
package com.wangwenjun.concurrent,chapter03; import java.util.stream.IntStream; public class ThreadYield { public static void main(String[] args) { IntStream.range(0, 2).mapToObj(ThreadYield::create) .forEach(Thread::start); } private static Thread create(int index) { return new Thread(() -> { //①注释部分 //if (index == 0) // Thread, yield(); System.out.printin(index); }); } }
上面的程序运行不少次,你会发现输出的结果不一致,有时候是0最早打印出来,有
时候是1最早打印出来,可是当你打开代码的注释部分,你会发现,顺序始终是0, 10 由于第一个线程若是最早得到了 CPU资源,它会比较谦虚,主动告诉CPU调度器是 放了本来属于本身的资源,可是yield R是一个提示(hint), CPU调度器并不会担保每次都 能知足yield提示。
yield sleep
看过前面的内容以后,会发现yield和sleep有一些混淆的地方,在JDK1.5之前的版本 中yield的方法事实上是调用了 sleep(O),可是它们之间存在着本质的区别,具体以下。
□ sleep会致使当前线程暂停指定的时间,没有CPU时间片的消耗。
□ yield只是对CPU调度器的一个提示,若是CPU调度器没有忽略这个提示,它会导
致线程上下文的切换。
□ sleep会使线程短暂block,会在给定的时间内释放CPU资源。
□ yield会使RUNNING状态的Thread进入RUNNABLE状态(若是CPU调度器没有 忽略这个提示的话)。
□ sleep几乎百分之百地完成了给定时间的休眠,而yield的提示并不能必定担保。
□ 一个线程sleep另外一个线程调用interrupt会捕获到中断信号,而yield则不会。
设置线程的优先级
□ public final void setPriority(int newPriority)为线程设定优先级。
□ public final int getPriority()获取线程的优先级。
线程优先级介绍
进程有进程的优先级,线程一样也有优先级,理论上是优先级比较高的线程会获取优 先被CPU调度的机会,可是事实上每每并不会如你所愿,设置线程的优先级一样也是一个 hint操做,具体以下。
□对于root用户,它会hint操做系统你想要设置的优先级别,不然它会被忽略。
□若是CPU比较忙,设置优先级可能会得到更多的CPU时间片,可是闲时优先级的 高低几乎不会有任何做用。
因此,不要在程序设计当中企图使用线程优先级绑定某些特定的业务,或者让业务严 重依赖于线程优先级,这可能会让你大失所望。举个简单的例子,可能不一样状况下的运行 效果不会彻底同样,可是咱们只是想让优先级比较高的线程得到更多的信息输出机会,示 例代码以下:
package com.wangwenjun.concurrent.chapter03; public class Threadpriority { public static void main(String[] args) { Thread tl = new Thread(()-> { while (true) { System.out.printin("tl"); } }); tl・ setPriority(3); Thread t2 = new Thread(()-> { while (true) { System. out. printin (,lt2"); } }); t2.setPriority(10); tl.start(); t2.start(); } }
运行上面的程序,会发现t2出现的频率很明显要高一些,固然这也和笔者当前CPU的 资源状况有关系,不一样状况下的运行会有不同的结果。
线程优先级源码分析
设置线程的优先级,只须要调用setPriority方法便可,下面咱们打开Thread的源码, 一块儿来分析一下:
public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) 1= null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriorityO(priority = newPriority); } }
经过上面源码的分析,咱们能够看出,线程的优先级不能小于1也不能大于10,若是 指定的线程优先级大于线程所在group的优先级,那么指定的优先级将会失效,取而代之 的是group的最大优先级,下面咱们经过一个例子来证实一下:
package com.wangwenjun.concurrent.chapter03; public class Threadpriority { public static void main(String[] args) { //定义一个线程组 ThreadGroup group = new ThreadGroup("test"); //将线程组的优先级指定为7 group.setMaxPriority(7); //定义一个线程,将该线程加入到group中 Thread thread = new Thread(group, "test-thread"); //企图将线程的优先级设定为10 thread.setPriority(10); / /企图未遂 System.out.printin(thread.getPriority()); } }
上面的结果输出为7,而不是10,由于它超过了所在线程组的优先级别
关于优先级的一些总结
通常状况下,不会对线程设定优先级别,更不会让某些业务严重地依赖线程的优先级 别,好比权重,借助优先级设定某个任务的权重,这种方式是不可取的,通常定义线程的 时候使用默认的优先级就行了,那么线程默认的优先级是多少呢?
线程默认的优先级和它的父类保持一致,通常状况下都是5,由于main线程的优先级 就是5,因此它派生出来的线程都是5,示例代码以下:
package com.wangwenjun.concurrent.chapter03; public class Threadpriority { public static void main(String[] args) { Thread tl = new Thread(); System.out,println("tl priority " + tl.getPriority()); Thread t2 = new Thread(()-> { Thread t3 = new Thread(); System.out.printIn("t3 priority " + t3.getPriority()); }); t2 ・ setPriority(6); t2 ・ start(); System.out.printin("t2 priority " + t2.getPriority());
上面程序的输出结果是tl的优先级为5,由于main线程的优先级是5 ; t2的优先级 是6,由于显式地将其指定为6; t3的优先级为6,没有显式地指定,所以其与父线程保持 一致。
获取线程ID
public long getld()获取线程的惟一 ID,线程的ID在整个JVM进程中都会是惟一的,
而且是从0开始逐次递增。若是你在main线程(main函数)中建立了一个惟一的线程,并 且调用getld()后发现其并不等于0,也许你会纳闷,不该该是从0开始的吗?以前已经说 过了在一个JVM进程启动的时候,其实是开辟了不少个线程,自增序列已经有了必定的 消耗,所以咱们本身建立的线程绝非第0号线程。
获取当前线程
public static Thread currentThread()用于返回当前执行线程的引用,这个方法虽然很简 单,可是使用很是普遍,咱们在后面的内容中会大量的使用该方法,来看一段示例代码:
package com.wangwenjun.concurrent.chapter03; public class CurrentThread { public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { //always true System.out.printin(Thread.CurrentThread() == this); } }; thread, start(); String name = Thread.CurrentThread().getName(); System.out.printIn("main".equals(name)); } }
上面程序运行输出的两个结果都是true。
设置线程上下文类加载器
□ public ClassLoader getContextClassLoader()获取线程上下文的类加载器,简单来讲 就是这个线程是由哪一个类加器加载的,若是是在没有修改线程上下文类加载器的情 况下,则保持与父线程一样的类加载器。
□ public void setContextClassLoader(ClassLoader cl)设置该线程的类加载器,这个方法 能够打破JAVA类加载器的父委托机制,有时候该方法也被称为JAVA类加载器的 后门。
关于线程上下文类加载器的内容咱们将在本书的第11章重点介绍,而且结合jdbc驱动 包的源码分析JDK的开发者为何要留有这样的后门。
线程 interrupt
线程interrupt,是一个很是重要的API,也是常用的方法,与线程中断相关的API 有以下几个,在本节中咱们也将Thread深刻源码对其进行详细的剖析。
□ public void interrupt() □ public static boolean interrupted() □ public boolean islnterrupted()
interrupt
以下方法的调用会使得当前线程进入阻塞状态,而调用当前线程的interrupt方法,就 能够打断阻塞。
□ Object 的 wait 方法。 □ Object 的 wait(long)方法。 □ Object 的 wait(long,int)方法。 □ Thread 的 sleep(long)方法。 □ Thread 的 sleep(long,int)方法。 □ Thread 的 join 方法。 □ Thread 的 join(long)方法。 □ Thread 的 join(long,int)方法。 □ InterruptibleChannel 的 io 操做。 □ Selector 的 wakeup 方法。
□其余方法。
上述若干方法都会使得当前线程进入阻塞状态,若另外的一个线程调用被阻塞线程的 interrupt方法,则会打断这种阻塞,所以这种方法有时会被称为可中断方法,.记住,打断一 个线程并不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。
一旦线程在阻塞的状况下被打断,都会抛出一个称为InterruptedException的异常,这 个异常就像一个signal (信号)同样通知当前线程被打断了,下面咱们来看一个例子:
package com.wangwenjun,concurrent,chapter03; import java.util.concurrent .TimeUnit; public class Threadinterrupt public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()-> { try { TimeUnit•MINUTES.sleep(1); } catch (InterruptedException e) { System.out.printIn("Oh, i am be interrupted."); } 、 }); thread.start(); //short block and make sure thread is started. TimeUnit, MILLISECONDS.sleep(2); thread, interrupt(); } }
上面的代码建立了一个线程,而且企图休眠1分钟的时长,不过很惋惜,大约在2毫 秒以后就被主线程调用interrupt方法打断,程序的执行结果就是“Oh, i am be interrupted.”
interrupt这个方法到底作了什么样的事情呢?在一个线程内部存在着名为interrupt flag 的标识,若是一个线程被interrupt,那么它的flag将被设置,可是若是当前线程正在执行 可中断方法被阻塞时,调用interrupt方法将其中断,反而会致使flag被清除,关于这点我 们在后面还会作详细的介绍。另外有一点须要注意的是,若是一个线程已是死亡状态, 那么尝试对其的interrupt会直接被忽略。
islnterrupted
islnterrupted是Thread的一个成员方法,它主要判断当前线程是否被中断,该方法仅 仅是对interrupt标识的一个判断,并不会影响标识发生任何改变,这个与咱们即将学习到 的interrupted是存在差异的,下面咱们看一个简单的程序:
package com.wangwenjun,concurrent•chapter03; import java.util.concurrent.TimeUnit; public class ThreadisInterrupted { public static void main(String[] args) throws InterruptedException Thread thread = new Thread() { @Override public void run() { while (true) { //do nothing, just empty loop. } } }; thread.start(); TimeUnit.MILLISEC0NDS.sleep(2); System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted()); thread.interrupt(); System.out.printf("Thread is interrupted ? %s\n", thread.islnterrupted()); } }
上面的代码中定义了一个线程,而且在线程的执行单元中(run方法)写了一个空的死 循环,为何不写sleep呢?由于sleep是可中断方法,会捕获到中断信号,从而干扰咱们 程序的结果。下面是程序运行的结果,记得手动结束上面的程序运行,或者你也能够将上 面定义的线程指定为守护线程,这样就会随着主线程的结束致使JVM中没有非守护线程而 自动退出。
Thread is interrupted ? false Thread is interrupted ? true
可中断方法捕获到了中断信号(signal)以后,也就是捕获了 InterruptedException异常 以后会擦除掉interrupt的标识,对上面的程序稍做修改,你会发现程序的结果又会出现很 大的不一样,示例代码以下:
package com.wangwenjun.concurrent.chapter03; import java.util.concurrent.TimeUnit; public class ThreadisInterrupted { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() while (true) try { TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { //ignore the exception //here the interrupt flag will be clear. System.out.printf("I am be interrupted ? %s\n", islnterrupted()); } } } }; thread.setDaemon(true); thread.start(); TimeUnit.MILLISECONDS.sleep(2); System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted()); thread.interrupt(); TimeUnit, MILLISECONDS・ sleep(2); System.out.printf("Thread is interrupted ? %s\n", thread.isInterrupted()); } }
因为在run方法中使用了 sleep这个可中断方法,它会捕获到中断信号,而且会擦除 interrupt标识,所以程序的执行结果都会是false,程序输岀以下:
Thread is interrupted ? false I am be interrupted ? false Thread is interrupted ? false
其实这也不难理解,可中断方法捕获到了中断信号以后,为了避免影响线程中其余方法 的执行,将线程的interrupt标识复位是一种很合理的设计。
interrupted
interrupted是一个静态方法,虽然其也用于判断当前线程是否被中断,可是它和成员方 法islnterrupted仍是有很大的区别的,调用该方法会直接擦除掉线程的interrupt标识,需 要注意的是,若是当前线程被打断了,那么第一次调用interrupted方法会返回true,而且 当即擦除了 interrupt标识;第二次包括之后的调用永远都会返回false,除非在此期间线程 又一次地被打断,下面设计了一个简单的例子,来验证咱们的说法:
package com.wangwenjun.concurrent.chapter03; import java.util.concurrent.TimeUnit; public class Threadinterrupted public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() { while (true) { System.out.printIn(Thread.interrupted()); } } }; thread.setDaemon(true); thread.start(); //shortly block make sure the thread is started. TimeUnit, MILLISECONDS.sleep(2); thread.interrupt(); } }
一样因为不想要受到可中断方法如sleep的影响,在Thread的run方法中没有进行任 何短暂的休眠,因此运行上面的程序会出现很是多的输出,可是咱们经过对输出的检查会 发现以下所示的内容,其足以做为对该方法的解释。
false false true false false
在不少的false包围中发现了一个true,也就是interrupted方法判断到了其被中断,立 即擦除了中断标识,而且只有这一次返回true,后面的都将会是false。
interrupt 注意事项
打开Thread的源码,不难发现,islnterrupted方法和interrupted方法都调用了同一个 本地方法:
private native boolean islnterrupted(boolean Clearlnterrupted);
其中参数Clearlnterrupted主要用来控制是否擦除线程interrupt的标识。
islnterrupted方法的源码中该参数为false,表示不想擦除:
public boolean islnterrupted() { return islnterrupted(false); }
而interrupted静态方法中该参数则为true,表示想要擦除:
public static boolean interrupted() { return currentThread().islnterrupted(true); }
在比较详细地学习了 interrupt方法以后,你们思考一个问题,若是一个线程在没有执 行可中断方法以前就被打断,那么其接下来将执行可中断方法,好比sleep会发生什么样的 状况呢?下面咱们经过一个简单的实验来回答这个疑问:
public static void main(String!] args) { //① 判断当前线程是否被中断 System.out.printin("Main thread is interrupted? " + Thread.interrupted()); //②中断当前线程 Thread.currentThread().interrupt(); //③判断当前线程是否已经被中断 System.out.printin("Main thread is interrupted? " + Thread.currentThread(). islnterrupted()); try { //④ 当前线程执行可中断方法 TimeUnit.MINUTES.sleep(1); } catch (InterruptedException e) { //⑤捕获中断信号 System.out.printin("I will be interrupted still."); } }
经过运行上面的程序,你会发现,若是一个线程设置了 interrupt标识,那么接下来的 可中断方法会当即中断,所以注释⑤的信号捕获部分代码会被执行,请你们注意注释①和注 释③中判断线程中断方法的不一样,也但愿读者结合本节的内容思考为何要这么作?
线程 join
Thread的join方法一样是一个很是重要的方法,使用它的特性能够实现不少比较强大 的功能,与sleep -样它也是一个可中断的方法,也就是说,若是有其余线程执行了对当前 线程的interrupt操做,它也会捕获到中断信号,而且擦除线程的interrupt标识,Thread的 API为咱们提供了三个不一样的join方法,具体以下。
□ public final void join() throws InterruptedException □ public final synchronized void join(long millis, int nanos) throws InterruptedException □ public final synchronized void join(long millis) throws InterruptedException
在本节中,笔者将会详细介绍join方法以及如何在实际应用中使用join方法。
线程join方法详解
join某个线程A,会使当前线程B进入等待,直到线程A结束生命周期,或者到达给 定的时间,那么在此期间B线程是处于BLOCKED的,而不是A线程,下面就来经过一个 简单的实例解释一下join方法的基本用法:
package com.wangwenjun.concurrent.chapter03; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import static java.util.stream.Collectors.toList; public class Threadjoin { public static void main(String[] args) throws InterruptedException { //①定义两个线程,并保存在threads中 List<Thread> threads = IntStream.range(1, 3) .mapToObj(Threadjoin::create).collect(toList()); //②启动这两个线程 threads•forEach(Thread::start); //③ 执行这两个线程的join方法 for (Thread thread : threads) { thread.join(); } //④main线程循环输出 for (int i = 0; i < 10; i++) System.out.printin(Thread.currentThread().getName() + "+ i); shortSleep(); //构造一个简单的线程,每一个线程只是简单的循环输出 private static Thread create(int seq) { return new Thread(() -> { for (int i = 0; i < 10; i++) { System・ out・ printin(Thread.currentThread()・ getName() + "#" + i); shortSleep(); } }, String.valueOf(seq)); } private static void shortSleep() { try { TimeUnit, SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面的代码结合Java 8的语法,建立了两个线程,分别启动,而且调用了每一个线程的 join方法(注意:join方法是被主线程调用的,所以在第一个线程尚未结束生命周期的时 后,第二个线程的join不会获得执行,可是此时,第二个线程也已经启动了),运行上面的 程序,你会发现线程一和线程二会交替地输出直到它们结束生命周期,main线程的循环才 会开始运行,程序输岀以下:
2#8 1#8 2#9 1#9 main#0 main#l main#2 main#3
若是你将注释③下面的join所有注释掉,那么三个线程将会交替地输出,程序输出以下:
main#2 2#2 1#2 main#3 1#3 2#3 main#4
join方法会使当前线程永远地等待下去,直到期间被另外的线程中断,或者join的线 程执行结束,固然你也可使用join的另外两个重载方法,指定毫秒数,在指定的时间到 达以后,当前线程也会退出阻塞。一样思考一个问题,若是一个线程已经结束了生命周期, 那么调用它的join方法的当前线程会被阻塞吗?
join方法结合实战
本节咱们将结合一个实际的案例,来看一下join方法的应用场景,假设你有一个APP, 主要用于查询航班信息,你的APP是没有这些实时数据的,当用户发起查询请求时,你需 要到各大航空公司的接口获取信息,最后统一整理加工返回到APP客户端,如图3-1所示, 固然JDK自带了不少高级工具,好比CountDownLatch和CyclicBarrier等均可以完成相似 的功能,可是仅就咱们目前所学的知识,使用join方法便可完成下面的功能。
该例子是典型的串行任务局部并行化处理,用户在APP客户端输入出发地“北京”和 目的地“上海”,服务器接收到这个请求以后,先来验证用户的信息,而后到各大航空公司 的接口查询信息,最后通过整理加工返回给客户端,每个航空公司的接口不会都同样, 获取的数据格式也不同,查询的速度也存在着差别,若是再跟航空公司进行串行化交互 (逐个地查询),很明显客户端须要等待很长的时间,这样的话,用户体验就会很是差。若是 咱们将每个航空公司的查询都交给一个线程去工做,而后在它们结束工做以后统一对数 据进行整理,这样就能够极大地节约时间,从而提升用户体验效果。
代码清单3-1 查询接口 FightQuery
package com.wangwenjun.concurrent.chapter03; import j ava.util.List; public interface FightQuery { List<String> get(); }
在代码清单3-1中,FightQuery提供了一个返回方法,写到这里你们应该注意到了,不 管是Thread的run方法,仍是Runnable接口,都是void返回类型,若是你想经过某个线 程的运行获得结果,就须要本身定义一个返回的接口。
查询Fight的task,其实就是一个线程的子类,主要用于到各大航空公司获取数据,示 例代码以下:
package com.wangwenjun,concurrent.chapter03; import java.util.ArrayList; import j ava.util.List; import j ava.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; public class FightQueryTask extends Thread implements FightQuery public FightQueryTask(String airline, String origin, String destination) super("[" + airline + "]"); this.origin = origin; this.destination = destination; } @Override public void run() { System.out.printf("%s-query from %s to %s \n", getName(), origin, destination); int randomVal = ThreadLocalRandom.current().nextlnt(10); try { TimeUnit.SECONDS.sleep(randomVal); this.flightList.add(getName() + + randomVal); System.out.printf("The Fight:%s list query successful\n", getName()); } catch (InterruptedException e) { } } @Override public List<String> get() { return this.flightList; } }
接口定义好了,查询航班数据的线程也有了,下面就来实现一下从SH (上海)到北京 (BJ)的航班查询吧!示例代码以下:
package com.wangwenjun.concurrent.chapter0 3; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static java.util.stream.Collectors.toList; public class FightQueryExample { //①合做的各大航空公司 private static List<String> fightCompany = Arrays.asList( "CSA", "CEA", "HNA" ); public static void main(String[] args) { List<String> results = search("SH", "BJ"); System.out.printin("===========result==========="); results , forEach(System•out::printin); } private static List<String> search(String original, String dest) { final List<String> result = new ArrayList<>(); //②建立查询航班信息的线程列表 List<FightQueryTask> tasks = fightCompany.stream() .map(f -> createSearchTask(f, original, dest)) .collect(toList()); //③分别启动这几个线程 tasks , forEach(Thread::start); 〃④分别调用每个线程的join方法,阻塞当前线程 tasks.forEach(t -> try t.join(); } catch (InterruptedException e) { } }); //⑤在此以前,当前线程会阻塞住,获取每个查询线程的结果,而且加入到result中 tasks.stream().map(FightQuery::get).forEach(result::addAll); return result; } FightQueryTask createSearchTask( fight, original, String dest) return new FightQueryTask(fight, original, dest); }
上面的代码,关键的地方已经过注释解释得很是清楚,主线程收到了 search请求以后, 交给了若干个查询线程分别进行工做,最后将每个线程获取的航班数据进行统一的汇总。 因为每一个航空公司的查询时间可能不同,因此用了一个随机值来反应不一样的查询速度, 返回给客户端(打印到控制台),程序的执行结果输出以下:
[CSA]-query from SH to BJ [CEA]-query from SH to BJ [HNA]-query from SH to BJ The Fight:[HNA] list query The Fights[CSA] list query The Fight:[CEA] list query ===========result=========: [CSA]-4 [CEA]-7 [HNA]-2
如何关闭一•个线程
JDK有一个Deprecated方法stop,可是该方法存在一个问题,JDK官方早已经不推荐 使用,其在后面的版本中有可能会被移除,根据官网的描述,该方法在关闭线程时可能不
会释放掉monitor的锁,因此强烈建议不要使用该方法结束线程,本节将主要介绍几种关闭 线程的方法。
正常关闭
\1. 线程结束生命周期正常结束
线程运行结束,完成了本身的使命以后,就会正常退出,若是线程中的任务耗时比较 短,或者时间可控,那么听任它正常结束就行了。
\2. 捕获中断信号关闭线程
咱们经过new Thread的方式建立线程,这种方式看似很简单,其实它的派生成本是比 较高的,所以在一个线程中每每会循环地执行某个任务,好比心跳检查,不断地接收网络 消息报文等,系统决定退出的时候,能够借助中断线程的方式使其退出,示例代码以下:
package com.wangwenjun.concurrent.chapter0 3; import java.util.concurrent. TimeUnit; public class InterruptThreadExit { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { @Override public void run() { System.out.printin("I will start work"); while (!islnterrupted()) { //working. } System.out.printin("I will be exiting."); } }; t.start(); TimeUnit.MINUTES・ sleep(1); System.out.printin("System will be shutdown."); t.interrupt(); } }
上面的代码是经过检查线程interrupt的标识来决定是否退出的,若是在线程中执行某 个可中断方法,则能够经过捕获中断信号来决定是否退出。
@Override public void run() System.out.printin("I will start work"); for (;;) //working, try TimeUnit.MILLISECONDS.sleep(l); catch (InterruptedException e) break; } } System.out.printin("I will be exiting."); }
上面的代码执行结果都会致使线程正常的结束,程序输出以下:
I will start work System will be shutdown. I will be exiting.
\3. 使用volatile开关控制
因为线程的interrupt标识颇有可能被擦除,或者逻辑单元中不会调用任何可中断方法, 因此使用volatile修饰的开关flag关闭线程也是一种经常使用的作法,具体以下:
package com.wangwenjun.concurrent.chapter03; import java.util•concurrent.TimeUnit; public class FlagThreadExit static class MyTask extends Thread private volatile boolean closed = false; @Override public void run() System.out.printIn("I will start work"); while (Iclosed && !islnterrupted()) //正在运行 } System.out.printin("I will be exiting."); public void close() { this.closed = true; this , interrupt(); } ? public static void main(String[] args) throws InterruptedException { MyTask t = new MyTask(); t.start(); TimeUnit・ MINUTES.sleep(1); System.out.printIn("System will be shutdown."); t.close(); } }
上面的例子中定义了一个closed开关变量,而且是使用volatile修饰(关于volatile关 键字会在本书的第3部分中进行很是细致地讲解,volatile关键字在Java中是一个革命性的 关键字,很是重要,它是Java原子变量以及并发包的基础)运行上面的程序一样也能够关 闭线程。
异常退出
在一个线程的执行单元中,是不容许抛出checked异常的,不论Thread中的run方 法,仍是Runnable中的run方法,若是线程在运行过程当中须要捕获checked异常而且 判断是否还有运行下去的必要,那么此时能够将checked异常封装成unchecked异常 (RuntimeException)抛出进而结束线程的生命周期。