189 8069 5689

什么是多线程源码

本篇内容主要讲解“什么是多线程源码”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“什么是多线程源码”吧!

成都网站制作、网站建设服务团队是一支充满着热情的团队,执着、敏锐、追求更好,是创新互联的标准与要求,同时竭诚为客户提供服务是我们的理念。创新互联公司把每个网站当做一个产品来开发,精雕细琢,追求一名工匠心中的细致,我们更用心!

线程 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 Threadsleeppublic 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");//将线程组的优先级指定为7group.setMaxPriority(7);//定义一个线程,将该线程加入到group中Thread thread = new Thread(group, "test-thread");//企图将线程的优先级设定为10thread.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(){@Overridepublic 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 Threadinterruptpublic 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 InterruptedExceptionThread thread = new Thread(){@Overridepublic 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 ? falseThread 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(){@Overridepublic 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 ? falseI am be interrupted ? falseThread 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 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 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("[">接口定义好了,查询航班数据的线程也有了,下面就来实现一下从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 fightCompany = Arrays.asList(
"CSA", "CEA", "HNA"
);
public static void main(String[] args)
{
List results = search("SH", "BJ");
System.out.printin("===========result===========");
results , forEach(System•out::printin);
}
private static List search(String original, String dest)
{
final List result = new ArrayList<>();
//②创建查询航班信息的线程列表
List 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)抛出进而结束线程的生命周期。

到此,相信大家对“什么是多线程源码”有了更深的了解,不妨来实际操作一番吧!这里是创新互联网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!


新闻名称:什么是多线程源码
网页地址:http://cdxtjz.cn/article/jidjdj.html

其他资讯