植物百科网
当前位置: 首页 农业百科

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

时间:2023-06-02 作者: 小编 阅读量: 2 栏目名: 农业百科

线程间通信的方式有哪些尽管通常每个子线程只需要完成自己的任务,但是有时我们可能希望多个线程一起完成一个任务,这涉及线程间的通信。三名运动员准备分开,然后在他们各自准备就绪后同时开始跑步。子线程完成任务后,它将结果返回给主线程。假设有两个线程:线程A和线程B。显然,我们需要更多细粒度地锁来控制执行顺序。B得到锁后打印1、2、3,然后调用该lock.notify()方法唤醒正在等待的A;唤醒后,A将继续打印其余的2、3。

线程间通信的方式有哪些?尽管通常每个子线程只需要完成自己的任务,但是有时我们可能希望多个线程一起完成一个任务,这涉及线程间的通信,今天小编就来聊一聊关于线程间通信的方式有哪些?接下来我们就一起去研究一下吧!

线程间通信的方式有哪些

尽管通常每个子线程只需要完成自己的任务,但是有时我们可能希望多个线程一起完成一个任务,这涉及线程间的通信。

该方法和本文中涉及的类是:thread.join(),object.wait(),object.notify(),CountdownLatch,CyclicBarrier,FutureTask,Callable等。

这是本文涵盖的代码

我将使用几个示例来说明如何在Java中实现线程间通信。

如何使两个线程按顺序执行?如何使两个线程以指定的方式有序相交?有四个线程:A,B,C和D(在A,B和C都完成执行并且A,B和C必须同步执行之前,不会执行D)。三名运动员准备分开,然后在他们各自准备就绪后同时开始跑步。子线程完成任务后,它将结果返回给主线程。

如何使两个线程按顺序执行?

假设有两个线程:线程A和线程B。两个线程都可以依次打印三个数字(1-3)。让我们看一下代码:

private static void demo1() {Thread A = new Thread(new Runnable() {@Overridepublic void run() {printNumber("A");}});Thread B = new Thread(new Runnable() {@Overridepublic void run() {printNumber("B");}});A.start();B.start();}

的实现printNumber(String)如下,用于依次打印1、2和3这三个数字:

private static void printNumber(String threadName) {int i=0;while (i++ < 3) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(threadName + "print:" + i);}}

我们得到的结果是:

B print: 1A print: 1B print: 2A print: 2B print: 3A print: 3

您可以看到A和B同时打印数字。

那么,如果我们希望B在A打印完之后开始打印呢?我们可以使用该thread.join()方法,代码如下:

private static void demo2() {Thread A = new Thread(new Runnable() {@Overridepublic void run() {printNumber("A");}});Thread B = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("B starts waiting for A");try {A.join();} catch (InterruptedException e) {e.printStackTrace();}printNumber("B");}});B.start();A.start();}

现在获得的结果是:

B starts waiting for AA print: 1A print: 2A print: 3 B print: 1B print: 2B print: 3

因此,我们可以看到该A.join()方法将使B等待直到A完成打印。

如何使两个线程以指定的方式有序地相交?

那么,如果现在我们希望B在A打印完1之后立即开始打印1,2,3,然后A继续打印2,3呢?显然,我们需要更多细粒度地锁来控制执行顺序。

在这里,我们可以利用object.wait()和object.notify()方法的优势。代码如下:

/** * A 1, B 1, B 2, B 3, A 2, A 3 */private static void demo3() {Object lock = new Object();Thread A = new Thread(new Runnable() {@Overridepublic void run() {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");}}});Thread B = new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {System.out.println("B 1");System.out.println("B 2");System.out.println("B 3");lock.notify();}}});A.start();B.start();}

结果如下:

A 1A waiting… B 1B 2B 3A 2A 3

那就是我们想要的。

怎么了?

首先,我们创建一个由A和B共享的对象锁: lock = new Object();当A得到锁时,它首先打印1,然后调用lock.wait()使它进入等待状态的方法,然后移交对锁的控制。在A调用lock.wait()释放控制的方法并且B获得锁之前,B将不会执行。B得到锁后打印1、2、3,然后调用该lock.notify()方法唤醒正在等待的A;唤醒后,A将继续打印其余的2、3。

我将日志添加到上面的代码中,以使其更易于理解。

private static void demo3() {Object lock = new Object();Thread A = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("INFO: A is waiting for the lock");synchronized (lock) {System.out.println("INFO: A got the lock");System.out.println("A 1");try {System.out.println("INFO: A is ready to enter the wait state, giving up control of the lock");lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("INFO: B wakes up A, and A regains the lock");System.out.println("A 2");System.out.println("A 3");}}});Thread B = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("INFO: B is waiting for the lock");synchronized (lock) {System.out.println("INFO: B got the lock");System.out.println("B 1");System.out.println("B 2");System.out.println("B 3");System.out.println("INFO: B ends printing, and calling the notify method");lock.notify();}}});A.start();B.start();

结果如下:

INFO: A is waiting for the lockINFO: A got the lockA 1INFO: A is ready to enter the wait state, giving up control of the lockINFO: B is waiting for the lockINFO: B got the lockB 1B 2B 3INFO: B ends printing, and calling the notify methodINFO: B wakes up A, and A regains the lockA 2A 3

在A,B和C都完成同步执行之后执行D

thread.join()前面介绍的方法允许一个线程在等待另一个线程完成运行之后继续执行。但是,如果我们将A,B和C顺序连接到D线程中,它将使A,B和C依次执行,而我们希望它们三个同步运行。

我们要达到的目标是:三个线程A,B和C可以同时开始运行,并且每个线程在独立运行完成后都将通知D。在A,B和C全部完成运行之后,D才会开始运行。因此,我们用于CountdownLatch实现这种类型的通信。其基本用法是:

  1. 创建一个计数器,并设置一个初始值, CountdownLatch countDownLatch = new CountDownLatch(3;
  2. countDownLatch.await()在等待线程中调用该方法,并进入等待状态,直到计数值变为0为止;否则,进入等待状态。
  3. countDownLatch.countDown()在其他线程中调用该方法,该方法会将计数值减少一;
  4. 当countDown()其他线程中的方法将计数值设为0时,countDownLatch.await()等待线程中的方法将立即退出并继续执行以下代码。

实现代码如下:

private static void runDAfterABC() {int worker = 3;CountDownLatch countDownLatch = new CountDownLatch(worker);new Thread(new Runnable() {@Overridepublic void run() {System.out.println("D is waiting for other three threads");try {countDownLatch.await();System.out.println("All done, D starts working");} catch (InterruptedException e) {e.printStackTrace();}}}).start();for (char threadName='A'; threadName <= 'C'; threadName++) {final String tN = String.valueOf(threadName);new Thread(new Runnable() {@Overridepublic void run() {System.out.println(tN + "is working");try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}System.out.println(tN + "finished");countDownLatch.countDown();}}).start();}}

结果如下:

D is waiting for other three threadsA is workingB is workingC is working A finishedC finishedB finishedAll done, D starts working

实际上,CountDownLatch它本身是一个倒数计数器,我们将初始计数值设置为3。运行D时,它首先调用该countDownLatch.await()方法以检查计数器值是否为0,如果该值不为0,它将保持等待状态。 。A,B和C分别countDownLatch.countDown()完成独立运行后,将使用该方法将倒数计数器递减1。当它们全部三个完成运行时,计数器将减少为0;否则,计数器将减少为0。然后,await()将触发D的方法以结束A,B和C,并且D将开始继续执行。

因此,CountDownLatch适用于一个线程需要等待多个线程的情况。

3名跑步者准备跑步

三个跑步者准备分开,然后在每个人准备就绪后同时开始跑步。

这次,三个线程A,B和C中的每个线程都需要分别进行准备,然后在三个线程全部准备好之后就开始同时运行。我们应该如何实现呢?

在CountDownLatch上面可以用来计数下降,但完成计数的时候,只有一个线程的一个await()方法会得到响应,所以多线程不能在同一时间被触发。

为了达到线程互相等待的效果,我们可以使用CyclicBarrier数据结构,其基本用法是:

  1. 首先创建一个公共对象CyclicBarrier,并设置同时等待的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
  2. 这些线程开始同时进行准备。准备好之后,他们需要等待其他人完成准备工作,因此调用该cyclicBarrier.await()方法来等待其他人;
  3. 当需要同时等待的指定线程全部调用该cyclicBarrier.await()方法时,这意味着这些线程已准备就绪,那么这些线程将开始继续同时执行。

实现代码如下。想象一下,有三位跑步者需要同时开始跑步,因此他们需要等待其他跑步者,直到所有人都准备就绪为止。

private static void runABCWhenAllReady() {int runner = 3;CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);final Random random = new Random();for (char runnerName='A'; runnerName <= 'C'; runnerName++) {final String rN = String.valueOf(runnerName);new Thread(new Runnable() {@Overridepublic void run() {long prepareTime = random.nextInt(10000) + 100;System.out.println(rN + "is preparing for time:" + prepareTime);try {Thread.sleep(prepareTime);} catch (Exception e) {e.printStackTrace();}try {System.out.println(rN + "is prepared, waiting for others");cyclicBarrier.await(); // The current runner is ready, waiting for others to be ready} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}System.out.println(rN + "starts running"); // All the runners are ready to start running together}}).start();}}

结果如下:

A is preparing for time: 4131B is preparing for time: 6349C is preparing for time: 8206 A is prepared, waiting for others B is prepared, waiting for others C is prepared, waiting for others C starts runningA starts runningB starts running

子线程将结果返回到主线程

在实际开发中,通常我们需要创建子线程来执行一些耗时的任务,然后将执行结果传递回主线程。那么如何在Java中实现呢?

因此,通常,在创建线程时,我们会将Runnable对象传递给Thread以便执行。Runnable的定义如下:

public interface Runnable {public abstract void run();}

您可以看到该run()方法执行后不返回任何结果。如果要返回结果怎么办?在这里,您可以使用另一个类似的接口类Callable:

@FunctionalInterfacepublic interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;}

可以看出,最大的区别Callable在于它返回了泛型。

因此,下一个问题是,如何将子线程的结果传递回去?Java有一个类,FutureTask可以与一起使用Callable,但是请注意,get用于获取结果的方法将阻塞主线程。

例如,我们希望子线程计算从1到100的总和,然后将结果返回给主线程。

private static void doTaskWithResultInWorker() {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {System.out.println("Task starts");Thread.sleep(1000);int result = 0;for (int i=0; i<=100; i++) {result += i;}System.out.println("Task finished and return result");return result;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);new Thread(futureTask).start();try {System.out.println("Before futureTask.get()");System.out.println("Result:" + futureTask.get());System.out.println("After futureTask.get()");} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}

结果如下:

Before futureTask.get() Task startsTask finished and return result Result: 5050After futureTask.get()

可以看出,当主线程调用该futureTask.get()方法时,它将阻塞主线程。然后Callable开始在内部执行并返回操作结果;然后futureTask.get()获取结果,主线程恢复运行。

在这里,我们可以了解到FutureTask和Callable可以直接在主线程中获得子线程的结果,但是它们会阻塞主线程。当然,如果您不想阻塞主线程,可以考虑使用ExecutorService将FutureTask线程放入线程池来管理执行。

概括

多线程是现代语言的常见功能,线程间通信,线程同步和线程安全是非常重要的主题。

    推荐阅读
  • 空气含量中最多的气体(空气含量中最多的气体介绍)

    空气中含量最多的气体是氮气,氮气约占空气体积分数的百分比约为78%。通过实验测定,空气的成分按体积计算,氮气大约占78%、氧气占21%、稀有气体0.94%、二氧化碳0.03%、其他气体和杂质0.03%,也就是说空气中含量最多的物质是氮气。氮气化学性质很不活泼,在高温高压及催化剂条件下才能和氢气反应生成氨气;在放电的情况下才能和氧气化合生成一氧化氮;即使Ca、Mg、Sr和Ba等活泼金属也只有在加热的情形下才能与其反应。

  • 文思豆腐羹如何做好吃(文思豆腐羹用什么豆腐)

    文思豆腐是一道有名的淮扬菜,需要的就是精湛的刀工,这样做出的文思豆腐会有嫩滑的口感,打造入口即化的口感。文思豆腐羹如何做好吃文思豆腐羹材料和做法步骤一、文思豆腐羹材料准备好豆腐400克,鸡脯肉,火腿还有香菇,再有准备好生菜,冬笋,调料需要准备盐和味精。

  • 结构性存款可以买理财吗(结构性存款是存款吗)

    雪球产品就是今年年初监管向信托公司进行窗口指导,要求叫停的产品。简单的说,这是一种高风险的金融衍生品,它通过持有一定结构的金融衍生品,来实现在某一特定情况下获利。这样的投资结构就能保证我不论涨,还是跌,只要在一定幅度内都可以盈利。交易期权等金融衍生品,是非常高风险的投资。

  • 正言厉色意思(正言厉色的意思)

    下面内容希望能帮助到你,我们来一起看看吧!正言厉色意思正言厉色,汉语成语,拼音是zhènɡyánlìsè,意思是形容板着脸,神情非常严厉。出自《汉书·王莽传》。宝玉突然想出一个主意,一本正经地给她讲扬州黛山林子洞耗子精偷香芋的故事,黛玉见他正言厉色,以为真有其事,后来才发现原来是在取笑她。

  • 面谈调薪酬有什么技巧(跟老板谈调薪的技巧有哪些)

    如果你在老板心目中分量很大,一般老板都会给你加薪的。和老板谈加薪时目的一定要明确,让老板知道你只是为了加薪,而不是辞职走人。和老板谈加薪后,一定要给老板一个考虑的时间,不要咄咄逼人,逼着老板加薪。老板也要有足够的思考时间,来考虑你是否值得加薪,给你加薪后对公司有没有什么影响。不仅口头上要表示感谢,工作中要更加努力,让老板觉得给你加薪是值得的。

  • 杏花有没有香味(杏花闻起来会特别香吗)

    杏树是中国著名的观赏树木,可配植于庭前、墙隅、道路旁、水边,也可群植、片植于山坡、水畔,是春季主要的观赏树种。杏花直径2至3厘米,先于叶开放。花梗短,长1至3毫米,被短柔毛。花萼紫绿色,萼筒圆筒形,外面基部被短柔毛。萼片卵形至卵状长圆形,先端急尖或圆钝,花后反折。花瓣圆形至倒卵形,白色或带红色,具短爪。

  • 减肥减肚子的方法(怎么减肚子呢)

    减肥减肚子的方法食用健康食品:酸奶与发酵的牛奶能激活消化必须的物质,有助于改善肠道微生物系统,从而防止腹部隆起。走路、喝水、按摩:走路及喝水有利腹部扁平。

  • 新坑翡翠手镯多少钱(新坑翡翠手镯的价格)

    新坑翡翠手镯多少钱?新坑翡翠手镯多少钱翡翠手镯作为大件翡翠制品,用料特别多,只有大块、质量好的翡翠原石才能打造成手镯,因此翡翠手镯的价格都比较高,商家们拿到质量比较好的原石也尽可能打造成手镯。具体到新坑种翡翠,因为大多数新坑种翡翠透明度都不高,质地也不够细腻,因此种水一般都是以糯种或豆种为主,极少出现冰种或冰种以上的种水,这样的翡翠价格价格自然不会太高,一个品质比较好的糯种翡翠手镯大概在十万以内。

  • 宁波毛蚶做法水煮几分钟(毛蚶煮多长时间可以吃)

    宁波毛蚶做法水煮几分钟毛蚬是很多人喜欢吃的食物,不过建议大家在做之前都要先用开水煮以下。强精益气,提高精液质量,增强精子活力。适用于治疗肾阳虚所致的阳痿、腰痛、小便频数及补五脏之气不足。可治疗全身水肿,小便不利等。能软化和保护血管,有降低人体中血脂和胆固醇的作用。

  • 2022洛阳湿地公园最新名单 洛阳生态公园最新消息

    国家级湿地自然保护区河南黄河湿地国家级自然保护区,面积24000公顷。国家级湿地公园嵩县陆浑湖国家湿地公园,面积4222.39公顷伊川伊河国家湿地公园,面积1384.36公顷。