线程间通信的方式有哪些(linux线程间通信的方法)

懵懂先生 投稿文章线程间通信的方式有哪些(linux线程间通信的方法)已关闭评论61阅读模式

文章源自略懂百科-http://wswcn.cn/114213.html

世界以痛吻我,要我报之以歌 —— 泰戈尔《飞鸟集》文章源自略懂百科-http://wswcn.cn/114213.html

虽然通常每个子线程只需要完成自己的任务,但是有时我们希望多个线程一起工作来完成一个任务,这就涉及到线程间通信。文章源自略懂百科-http://wswcn.cn/114213.html

关于线程间通信本文涉及到的方法和类包括:thread.join()、object.wait()、object.notify()、CountdownLatch、CyclicBarrier、FutureTask、Callable。文章源自略懂百科-http://wswcn.cn/114213.html

接下来将用几个例子来介绍如何在Java中实现线程间通信:文章源自略懂百科-http://wswcn.cn/114213.html

如何让两个线程依次执行,即一个线程等待另一个线程执行完成后再执行?如何让两个线程以指定的方式有序相交执行?有四个线程:A、B、C、D,如何实现 D 在 A、B、C 都同步执行完毕后执行?三个运动员分开准备,然后在每个人准备好后同时开始跑步。子线程完成任务后,将结果返回给主线程。文章源自略懂百科-http://wswcn.cn/114213.html

1. 如何让两个线程依次执行?

假设有两个线程:A 和 B,这两个线程都可以按照顺序打印数字,代码如下:文章源自略懂百科-http://wswcn.cn/114213.html

publicclassTest01{publicstaticvoidmain(String[] args) throws InterruptedException{
demo1();
}publicstaticvoiddemo1(){
Thread a =newThread(() -> {
printNumber("A");
});文章源自略懂百科-http://wswcn.cn/114213.html

Thread b =newThread(() -> {
printNumber("B");
});文章源自略懂百科-http://wswcn.cn/114213.html

a.start();
b.start();
}publicstaticvoidprintNumber(String threadName){inti =0;while(i++ <3) {try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName +" print: "+ i);
}
}文章源自略懂百科-http://wswcn.cn/114213.html

}文章源自略懂百科-http://wswcn.cn/114213.html

复制代码文章源自略懂百科-http://wswcn.cn/114213.html

得到的结果如下:文章源自略懂百科-http://wswcn.cn/114213.html

A print:1B print:1B print:2A print:2A print:3B print:3复制代码文章源自略懂百科-http://wswcn.cn/114213.html

可以看到 A 和 B 同时打印数字,如果我们希望 B 在 A 执行完成之后开始执行,那么可以使用 thread.join() 方法实现,代码如下:文章源自略懂百科-http://wswcn.cn/114213.html

public static void demo2() {
Thread a =newThread(()->{
printNumber("A");
});文章源自略懂百科-http://wswcn.cn/114213.html

Thread b =newThread(()->{
System.out.println("B 等待 A 执行");try{
a.join();
}catch(InterruptedException e) {
e.printStackTrace();
}
printNumber("B");
});文章源自略懂百科-http://wswcn.cn/114213.html

a.start();
b.start();
}文章源自略懂百科-http://wswcn.cn/114213.html

复制代码文章源自略懂百科-http://wswcn.cn/114213.html

得到的结果如下:文章源自略懂百科-http://wswcn.cn/114213.html

B等待A执行A print:1A print:2A print:3B print:1B print:2B print:3复制代码文章源自略懂百科-http://wswcn.cn/114213.html

我们可以看到该 a.join() 方法会让 B 等待 A 完成打印。文章源自略懂百科-http://wswcn.cn/114213.html

thread.join() 方法的作用就是阻塞当前线程,等待调用 join() 方法的线程执行完毕后再执行后面的代码。文章源自略懂百科-http://wswcn.cn/114213.html

查看 join() 方法的源码,内部是调用了 join(0) ,如下:文章源自略懂百科-http://wswcn.cn/114213.html

publicfinalvoidjoin()throwsInterruptedException{
join(0);
}
复制代码文章源自略懂百科-http://wswcn.cn/114213.html

查看 join(0) 的源码如下:文章源自略懂百科-http://wswcn.cn/114213.html

// 注意这里使用了 sychronized 加锁,锁对象是线程的实例对象publicfinalsynchronizedvoidjoin(longmillis)throwsInterruptedException{longbase = System.currentTimeMillis();longnow =0;if(millis <0) {thrownewIllegalArgumentException("timeout value is negative");
}// 调用 join(0) 执行下面的代码if(millis ==0) {// 这里使用 while 循环的目的是为了避免虚假唤醒// 如果当前线程存活则调用 wait(0), 0 表示永久等待,直到调用 notifyAll() 或者 notify() 方法// 当线程结束的时候会调用 notifyAll() 方法while(isAlive()) {
wait(0);
}
}else{while(isAlive()) {longdelay = millis - now;if(delay <=0) {break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
复制代码文章源自略懂百科-http://wswcn.cn/114213.html

从源码中可以看出 join(long millis) 方法是通过 wait(long timeout) (Object 提供的方法)方法实现的,调用 wait 方法之前,当前线程必须获得对象的锁,所以此 join 方法使用了 synchronized 加锁,锁对象是线程的实例对象。其中 wait(0)方法会让当前线程阻塞等待,直到另一个线程调用此对象的 notify() 或者 notifyAll() 方法才会继续执行。当调用 join 方法的线程结束的时候会调用 notifyAll() 方法,所以 join() 方法可以实现一个线程等待另一个调用 join() 的线程结束后再执行。文章源自略懂百科-http://wswcn.cn/114213.html

虚假唤醒:一个线程在没有被通知、中断、超时的情况下被唤醒;文章源自略懂百科-http://wswcn.cn/114213.html

虚假唤醒可能导致条件不成立的情况下执行代码,破坏被锁保护的约束关系;文章源自略懂百科-http://wswcn.cn/114213.html

为什么使用 while 循环来避免虚假唤醒文章源自略懂百科-http://wswcn.cn/114213.html

在 if 块中使用 wait 方法,是非常危险的,因为一旦线程被唤醒,并得到锁,就不会再判断 if 条件而执行 if 语句块外的代码,所以建议凡是先要做条件判断,再 wait 的地方,都使用 while 循环来做,循环会在等待之前和之后对条件进行测试。文章源自略懂百科-http://wswcn.cn/114213.html

2. 如何让两个线程按照指定的方式有序相交?

如果现在我们希望 B线程在 A 线程打印 1 后立即打印 1,2,3,然后 A 线程继续打印 2,3,那么我们需要更细粒度的锁来控制执行顺序。文章源自略懂百科-http://wswcn.cn/114213.html

在这里,我们可以利用 object.wait() 和 object.notify() 方法,代码如下:文章源自略懂百科-http://wswcn.cn/114213.html

publicstaticvoiddemo3(){
Objectlock=newObject();
Thread A =newThread(() -> {
synchronized (lock) {
System.out.println("A 1");try{lock.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("A 2");
System.out.println("A 3");
}
});文章源自略懂百科-http://wswcn.cn/114213.html

Thread B =newThread(() -> {
synchronized (lock) {
System.out.println("B 1");
System.out.println("B 2");
System.out.println("B 3");lock.notify();
}
});文章源自略懂百科-http://wswcn.cn/114213.html

A.start();
B.start();
}
复制代码文章源自略懂百科-http://wswcn.cn/114213.html

得到的结果如下:文章源自略懂百科-http://wswcn.cn/114213.html

A1B1B2B3A2A3复制代码文章源自略懂百科-http://wswcn.cn/114213.html

上述代码的执行流程如下:文章源自略懂百科-http://wswcn.cn/114213.html

首先我们创建一个由 A 和 B 共享的对象锁: lock = new Object();当A拿到锁时,先打印1,然后调用lock.wait()方法进入等待状态,然后交出锁的控制权;B 不会被执行,直到 A 调用该lock.wait()方法释放控制权并且 B 获得锁;B拿到锁后打印1,2,3,然后调用lock.notify()方法唤醒正在等待的A;A 唤醒后继续打印剩余的 2,3。文章源自略懂百科-http://wswcn.cn/114213.html

为了便于理解,我将上面的代码添加了日志,代码如下:文章源自略懂百科-http://wswcn.cn/114213.html

publicstaticvoiddemo3(){
Objectlock=newObject();
Thread A =newThread(() -> {
System.out.println("INFO:A 等待获取锁");
synchronized (lock) {
System.out.println("INFO:A 获取到锁");
System.out.println("A 1");try{
System.out.println("INFO:A 进入 waiting 状态,放弃锁的控制权");lock.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("INFO:A 被 B 唤醒继续执行");
System.out.println("A 2");
System.out.println("A 3");
}
});文章源自略懂百科-http://wswcn.cn/114213.html

Thread B =newThread(() -> {
System.out.println("INFO:B 等待获取锁");
synchronized (lock) {
System.out.println("INFO:B 获取到锁");
System.out.println("B 1");
System.out.println("B 2");
System.out.println("B 3");
System.out.println("INFO:B 执行结束,调用 notify 方法唤醒 A");lock.notify();
}
});文章源自略懂百科-http://wswcn.cn/114213.html

A.start();
B.start();
}
复制代码文章源自略懂百科-http://wswcn.cn/114213.html

得到的结果如下:文章源自略懂百科-http://wswcn.cn/114213.html

INFO:A等待获取锁INFO:A获取到锁A1INFO:A进入 waiting 状态,放弃锁的控制权INFO:B等待获取锁INFO:B获取到锁B1B2B3INFO:B执行结束,调用 notify 方法唤醒 AINFO:A被 B 唤醒继续执行A2A3复制代码文章源自略懂百科-http://wswcn.cn/114213.html

3. 线程 D 在A、B、C都同步执行完毕后执行

thread.join() 前面介绍的方法允许一个线程在等待另一个线程完成运行后继续执行。但是如果我们将A、B、C依次加入到D线程中,就会让A、B、C依次执行,而我们希望它们三个同步运行。文章源自略懂百科-http://wswcn.cn/114213.html

我们要实现的目标是:A、B、C三个线程可以同时开始运行,各自独立运行完成后通知D;D 不会开始运行,直到 A、B 和 C 都运行完毕。所以我们 CountdownLatch 用来实现这种类型的通信。它的基本用法是:文章源自略懂百科-http://wswcn.cn/114213.html

创建一个计数器,并设置一个初始值, CountdownLatch countDownLatch = new CountDownLatch(3);调用countDownLatch.await()进入等待状态,直到计数值变为0;在其他线程调用countDownLatch.countDown(),该方法会将计数值减一;当计数器的值变为 0 时,countDownLatch.await()等待线程中的方法会继续执行下面的代码。文章源自略懂百科-http://wswcn.cn/114213.html

实现代码如下:文章源自略懂百科-http://wswcn.cn/114213.html

publicstaticvoidrunDAfterABC(){intcount =3;
CountDownLatch countDownLatch =newCountDownLatch(count);newThread(() -> {
System.out.println("INFO: D 等待 A B C 运行完成");try{
countDownLatch.await();
System.out.println("INFO: A B C 运行完成,D 开始运行");
System.out.println("D is working");
}catch(InterruptedException e) {
e.printStackTrace();
}
}).start();for(charthreadName =A; threadName <=C; threadName++) {
final String name = String.valueOf(threadName);newThread(() -> {
System.out.println(name +" is working");try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(name +" finished");
countDownLatch.countDown();
}).start();
}
}
复制代码文章源自略懂百科-http://wswcn.cn/114213.html

得到的结果如下:文章源自略懂百科-http://wswcn.cn/114213.html

INFO:D 等待 A B C 运行完成Ais workingBis workingCis workingCfinishedBfinishedAfinishedINFO:A B C 运行完成,D 开始运行Dis working复制代码文章源自略懂百科-http://wswcn.cn/114213.html

其实CountDownLatch它本身就是一个倒数计数器,我们把初始的count值设置为3。D运行的时候,首先调用该countDownLatch.await()方法检查计数器的值是否为0,如果不是0则保持等待状态. A、B、C 运行完毕后,分别使用countDownLatch.countDown()方法将倒数计数器减1。计数器将减为 0,然后通知await()方法结束等待,D开始继续执行。文章源自略懂百科-http://wswcn.cn/114213.html

因此,CountDownLatch适用于一个线程需要等待多个线程的情况。文章源自略懂百科-http://wswcn.cn/114213.html

4. 三个运动员分开准备同时开跑

这一次,A、B、C这三个线程都需要分别准备,等三个线程都准备好后开始同时运行,我们应该如何做到这一点?文章源自略懂百科-http://wswcn.cn/114213.html

CountDownLatch可以用来计数,但完成计数的时候,只有一个线程的一个await()方法会得到响应,所以多线程不能在同一时间被触发。为了达到线程相互等待的效果,我们可以使用该CyclicBarrier,其基本用法为:文章源自略懂百科-http://wswcn.cn/114213.html

首先创建一个公共对象CyclicBarrier,并设置同时等待的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);这些线程同时开始准备,准备好后,需要等待别人准备好,所以调用cyclicBarrier.await()方法等待别人;当指定的需要同时等待的线程都调用了该cyclicBarrier.await()方法时,意味着这些线程准备好了,那么这些线程就会开始同时继续执行。文章源自略懂百科-http://wswcn.cn/114213.html

想象一下有三个跑步者需要同时开始跑步,所以他们需要等待其他人都准备好,实现代码如下:文章源自略懂百科-http://wswcn.cn/114213.html

publicstaticvoidrunABCWhenAllReady(){intcount =3;
CyclicBarrier cyclicBarrier =newCyclicBarrier(count);
Random random =newRandom();for(charthreadName =A; threadName <=C; threadName++) {
final String name = String.valueOf(threadName);newThread(() -> {intprepareTime = random.nextInt(10000);
System.out.println(name +" 准备时间:"+ prepareTime);try{
Thread.sleep(prepareTime);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(name +" 准备好了,等待其他人");try{
cyclicBarrier.await();
}catch(InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(name +" 开始跑步");
}).start();
}
}
复制代码文章源自略懂百科-http://wswcn.cn/114213.html

得到结果如下:文章源自略懂百科-http://wswcn.cn/114213.html

A准备时间:1085B准备时间:7729C准备时间:8444A准备好了,等待其他人B准备好了,等待其他人C准备好了,等待其他人C开始跑步A开始跑步B开始跑步复制代码文章源自略懂百科-http://wswcn.cn/114213.html

CyclicBarrier 的作用就是等待多个线程同时执行。文章源自略懂百科-http://wswcn.cn/114213.html

5. 子线程将结果返回给主线程

在实际开发中,往往我们需要创建子线程来做一些耗时的任务,然后将执行结果传回主线程。那么如何在 Java 中实现呢?文章源自略懂百科-http://wswcn.cn/114213.html

一般在创建线程的时候,我们会把 Runnable 对象传递给 Thread 执行,Runable 的源码如下:文章源自略懂百科-http://wswcn.cn/114213.html

@FunctionalInterfacepublicinterfaceRunnable{publicabstractvoidrun();
}文章源自略懂百科-http://wswcn.cn/114213.html

复制代码文章源自略懂百科-http://wswcn.cn/114213.html

可以看到 Runable 是一个函数式接口,该接口中的 run 方法没有返回值,那么如果要返回结果,可以使用另一个类似的接口 Callable。文章源自略懂百科-http://wswcn.cn/114213.html

函数式接口:只有一个方法的接口文章源自略懂百科-http://wswcn.cn/114213.html

Callable 接口的源码如下:文章源自略懂百科-http://wswcn.cn/114213.html

@FunctionalInterfacepublicinterfaceCallable<V>{/**
* Computes a result, or throws an exception if unable to do so.
*
*@returncomputed result
*@throwsException if unable to compute a result
*/Vcall()throwsException;
}
复制代码文章源自略懂百科-http://wswcn.cn/114213.html

可以看出,最大的区别Callable在于它返回的是泛型。文章源自略懂百科-http://wswcn.cn/114213.html

那么接下来的问题是,如何将子线程的结果传回去呢?Java 有一个类,FutureTask,它可以与 一起工作Callable,但请注意,get用于获取结果的方法会阻塞主线程。FutureTask 本质上还是一个 Runnable,所以可以直接传到 Thread 中。文章源自略懂百科-http://wswcn.cn/114213.html

比如我们想让子线程计算1到100的总和,并将结果返回给主线程,代码如下:文章源自略懂百科-http://wswcn.cn/114213.html

publicstaticvoidgetResultInWorker(){
Callable callable = () -> {
System.out.println("子任务开始执行");
Thread.sleep(1000);intresult =0;for(inti =0; i <=100; i++) {
result += i;
}
System.out.println("子任务执行完成并返回结果");returnresult;
};
FutureTask futureTask =newFutureTask<>(callable);newThread(futureTask).start();try{
System.out.println("开始执行 futureTask.get()");
Integer result = futureTask.get();
System.out.println("执行的结果:"+ result);
}catch(InterruptedException e) {
e.printStackTrace();
}catch(ExecutionException e) {
e.printStackTrace();
}
}文章源自略懂百科-http://wswcn.cn/114213.html

复制代码文章源自略懂百科-http://wswcn.cn/114213.html

得到的结果如下:文章源自略懂百科-http://wswcn.cn/114213.html

开始执行 futureTask.get()
子任务开始执行
子任务执行完成并返回结果
执行的结果:5050复制代码文章源自略懂百科-http://wswcn.cn/114213.html

可以看出在主线程调用futureTask.get()方法时阻塞了主线程;然后Callable开始在内部执行并返回操作的结果;然后futureTask.get()得到结果,主线程恢复运行。文章源自略懂百科-http://wswcn.cn/114213.html

在这里我们可以了解到,FutureTask和Callable可以直接在主线程中获取子线程的结果,但是它们会阻塞主线程。当然,如果你不希望阻塞主线程,可以考虑使用ExecutorService把FutureTask到线程池来管理执行。文章源自略懂百科-http://wswcn.cn/114213.html

作者:惜鸟链接:https://juejin.cn/post/7004401589385609246文章源自略懂百科-http://wswcn.cn/114213.html

文章源自略懂百科-http://wswcn.cn/114213.html

懵懂先生
  • 本文由 发表于 2023年8月29日 21:20:41
  • 转载请注明:http://wswcn.cn/114213.html
投稿文章

热文英特那雄纳尔意思是什么(英特那雄纳尔是什么意思)

昨天上午,庆祝中国共产党成立100周年大会上,人们全体起立高唱《国际歌》:起来,饥寒交迫的奴隶,起来,全世界受苦的人!......团结起来到明天,英特纳雄耐尔就一定要实现! 《国际歌》(LIntern...
投稿文章

苹果售后电话(附近苹果手机修理店地址)

附近美的空调售后维修电话400-153-5118(24小时全国客服中心) 美的空调全国报修热线: 400-1535-118//〔全市24小时)客户服务中心 欢迎进入♀美的空调各点-售后服务网站=电话 ...
投稿文章

朋友圈发什么才能吸引人

一、如果我没有什么可以帮到你,至少可以陪着你。 二、时间不会让我忘记你,只会习惯没有你。 三、你一定要站在自己所热爱的世界里闪闪发亮,这归途尚远,要迷人、且倔强。 四、生活就是生的不尽人意,活的惊天动...
投稿文章

做纯小米锅巴窍门

家里有小米的试试做锅巴,酥脆香辣,吃一口就忘不了,方法超简单 从小到大,我都特别喜欢吃锅巴,每隔几天,都会买上一袋,一边追剧,一边嚼着它,那种感觉真的特别惬意。酥脆的锅巴,加上丰富的口感,越嚼越香,越...