java学习基地

微信扫一扫 分享朋友圈

已有 1560 人浏览分享

你知道 Java 是如何实现线程间通信的吗?

[复制链接]
1560 0

一般状况下,每一个子线程完成各自的使命就能够完毕了。不外有的时分,我们期望多个线程协同事情去完成某个使命,这时候便触及到了线程间通讯了。

本文触及到的常识面:

  • thread.join(),
  • object.wait(),
  • object.notify(),
  • CountdownLatch,
  • CyclicBarrier,
  • FutureTask,
  • Callable 。

本文触及代码:
https://github.com/wingjay/Hello ... src/ForArticle.java

上面我从寂例子做为切进面来说解下 Java 里有哪些办法去完成线程间通讯。

  • 怎样让两个线程顺次施行?

  • 那怎样让 两个线程根据指定方法又跪穿插运转呢?

  • 四个线程 A B C D,此中 D 要比及 A B C 齐施行终了后卜蚀止,并且 A B C 是同步运转的

  • 三个活动员各自筹办,比及三小我私家皆筹办好后,再一同跑

  • 子线程完成某件使命后,把获得的成果回传给主线程


怎样让两个线程顺次施行?

假定有两个线程,一个是线程 A,另外一个是线程 B,两个线程别离顺次挨印 1-3 三个数字便可。我们去看下代码:

  1. private static void demo1() {  
  2.     Thread A = new Thread(new Runnable() {  
  3.         @Override  
  4.         public void run() {  
  5.             printNumber("A");  
  6.         }  
  7.     });  
  8.     Thread B = new Thread(new Runnable() {  
  9.         @Override  
  10.         public void run() {  
  11.             printNumber("B");  
  12.         }  
  13.     });  
  14.     A.start();  
  15.     B.start();  
  16. }  
赶钙代码
此中的 printNumber(String) 完成以下,雍么顺次挨印 1, 2, 3 三个数字:
  1. private static void printNumber(String threadName) {  
  2.     int i=0;  
  3.     while (i++ < 3) {  
  4.         try {  
  5.             Thread.sleep(100);  
  6.         } catch (InterruptedException e) {  
  7.             e.printStackTrace();  
  8.         }  
  9.         System.out.println(threadName + " print: " + i);  
  10.     }  
  11. }  
赶钙代码

这时候我们获得的成果是:

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

能够看到 A 战 B 是同时挨印的。

那末,假如我们期望 B 正在 A 局部挨印 完后再开端挨印呢?我们能够操纵 thread.join() 办法,代码以下:

  1. private static void demo2() {  
  2.     Thread A = new Thread(new Runnable() {  
  3.         @Override  
  4.         public void run() {  
  5.             printNumber("A");  
  6.         }  
  7.     });  
  8.     Thread B = new Thread(new Runnable() {  
  9.         @Override  
  10.         public void run() {  
  11.             System.out.println("B 开端等候 A");  
  12.             try {  
  13.                 A.join();  
  14.             } catch (InterruptedException e) {  
  15.                 e.printStackTrace();  
  16.             }  
  17.             printNumber("B");  
  18.         }  
  19.     });  
  20.     B.start();  
  21.     A.start();  
  22. }  
赶钙代码

获得的成果以下:

B 开端等候 A
A print: 1
A print: 2
A print: 3

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

以是我们能看到 A.join() 办法会让 B 不断等候曲到 A 运转终了。

那怎样让 两个线程根据指定方法又跪穿插运转呢?

仍是上里谁人例子,我如今期望 A 正在挨印完 1 后,再让 B 挨印 1, 2, 3,最初再回到 A 持续挨印 2, 3。这类需供下,明显 Thread.join() 曾经不克不及满意了。我们需求更细粒度的锁去掌握施行挨次。

那里,我们能够操纵 object.wait() 战 object.notify() 两个办法去完成。代码以下:

  1. /**  
  2. * A 1, B 1, B 2, B 3, A 2, A 3  
  3. */  
  4. private static void demo3() {  
  5.     Object lock = new Object();  
  6.     Thread A = new Thread(new Runnable() {  
  7.         @Override  
  8.         public void run() {  
  9.             synchronized (lock) {  
  10.                 System.out.println("A 1");  
  11.                 try {  
  12.                     lock.wait();  
  13.                 } catch (InterruptedException e) {  
  14.                     e.printStackTrace();  
  15.                 }  
  16.                 System.out.println("A 2");  
  17.                 System.out.println("A 3");  
  18.             }  
  19.         }  
  20.     });  
  21.     Thread B = new Thread(new Runnable() {  
  22.         @Override  
  23.         public void run() {  
  24.             synchronized (lock) {  
  25.                 System.out.println("B 1");  
  26.                 System.out.println("B 2");  
  27.                 System.out.println("B 3");  
  28.                 lock.notify();  
  29.             }  
  30.         }  
  31.     });  
  32.     A.start();  
  33.     B.start();  
  34. }  
赶钙代码

挨印成果以下:

A 1
A waiting…

B 1
B 2
B 3
A 2
A 3

恰是我们要的成果。

那末,那个历程发作了甚么呢?

  • 起首创立一个 A 战 B 同享的工具锁 lock = new Object();

  • 当 A 获得锁后,先挨印 1,然后挪用 lock.wait() 办法,交出锁的掌握权,进进 wait 形态;

  • 对 B 而行,因为 A 最开端获得了锁,招致 B 没法施行;曲到 A 挪用 lock.wait() 开释掌握权后, B 才获得了锁;

  • B 正在获得锁后挨印 1, 2, 3;然后挪用 lock.notify() 办法,叫醒正正在 wait 的 A;

  • A 被叫醒后,持续挨印剩下的 2,3。


为了更汉庙解,我正在上里的代码里减上 log 便利读者检察。

  1. private static void demo3() {  
  2.     Object lock = new Object();  
  3.     Thread A = new Thread(new Runnable() {  
  4.         @Override  
  5.         public void run() {  
  6.             System.out.println("INFO: A 等候锁 ");  
  7.             synchronized (lock) {  
  8.                 System.out.println("INFO: A 获得了锁 lock");  
  9.                 System.out.println("A 1");  
  10.                 try {  
  11.                     System.out.println("INFO: A 筹办进进等候形态,抛却锁 lock 的掌握权 ");  
  12.                     lock.wait();  
  13.                 } catch (InterruptedException e) {  
  14.                     e.printStackTrace();  
  15.                 }  
  16.                 System.out.println("INFO: 有人叫醒了 A, A 从头得到锁 lock");  
  17.                 System.out.println("A 2");  
  18.                 System.out.println("A 3");  
  19.             }  
  20.         }  
  21.     });  
  22.     Thread B = new Thread(new Runnable() {  
  23.         @Override  
  24.         public void run() {  
  25.             System.out.println("INFO: B 等候锁 ");  
  26.             synchronized (lock) {  
  27.                 System.out.println("INFO: B 获得了锁 lock");  
  28.                 System.out.println("B 1");  
  29.                 System.out.println("B 2");  
  30.                 System.out.println("B 3");  
  31.                 System.out.println("INFO: B 挨印终了,挪用 notify 办法 ");  
  32.                 lock.notify();  
  33.             }  
  34.         }  
  35.     });  
  36.     A.start();  
  37.     B.start();  
  38. }  
赶钙代码

挨印成果以下:

INFO: A 等候锁
INFO: A 获得了锁 lock
A 1
INFO: A 筹办进进等候形态,挪用 lock.wait() 抛却锁 lock 的掌握权
INFO: B 等候锁
INFO: B 获得了锁 lock
B 1
B 2
B 3
INFO: B 挨印终了,挪用 lock.notify() 办法
INFO: 有人叫醒了 A, A 从头得到锁 lock
A 2
A 3

四个线程 A B C D,此中 D 要比及 A B C 齐施行终了后卜蚀止,并且 A B C 是同步运转的

最开端我们引见了 thread.join(),可让一个线程等另外一个线程运转终了后再持续施行,那我们能够正在 D 线程里顺次 join A B C,不外那也便使得 A B C 必需顺次施行,而我们要的是那三者能同步运转。

大概道,我们期望到达的目标是:A B C 三个线程同时运转,各捉坐运转完后告诉 D1 D 而行,只需 A B C 皆运转完了,D 再开端运转。针对这类状况,我们能够操纵 CountdownLatch 去完成那类通讯方法。它的根本用法是:

  • 创立一个计数器,设置初初值,CountdownLatch countDownLatch = new CountDownLatch(2);

  • 正在 等候线程 里挪用 countDownLatch.await() 办法,进进等候形态,曲到计数值酿成 0;

  • 正在 其他线程 里,挪用 countDownLatch.countDown() 办法,该办法会将计数值加小 1;

  • 当 其他线程 的 countDown() 办法把计数值酿成 0 时,等候线程 里的 countDownLatch.await() 立刻湍骣,持续施行上面的代码。


完成代码以下:

  1. private static void runDAfterABC() {  
  2.     int worker = 3;  
  3.     CountDownLatch countDownLatch = new CountDownLatch(worker);  
  4.     new Thread(new Runnable() {  
  5.         @Override  
  6.         public void run() {  
  7.             System.out.println("D is waiting for other three threads");  
  8.             try {  
  9.                 countDownLatch.await();  
  10.                 System.out.println("All done, D starts working");  
  11.             } catch (InterruptedException e) {  
  12.                 e.printStackTrace();  
  13.             }  
  14.         }  
  15.     }).start();  
  16.     for (char threadName='A'; threadName <= 'C'; threadName++) {  
  17.         final String tN = String.valueOf(threadName);  
  18.         new Thread(new Runnable() {  
  19.             @Override  
  20.             public void run() {  
  21.                 System.out.println(tN + " is working");  
  22.                 try {  
  23.                     Thread.sleep(100);  
  24.                 } catch (Exception e) {  
  25.                     e.printStackTrace();  
  26.                 }  
  27.                 System.out.println(tN + " finished");  
  28.                 countDownLatch.countDown();  
  29.             }  
  30.         }).start();  
  31.     }  
  32. }  
赶钙代码

上面实了止成果:

D is waiting for other three threads
A is working
B is working
C is working

A finished
C finished
B finished
All done, D starts working

实在简朴面来讲,CountDownLatch 便是一个倒计数器,我们把初初计数值设置为3,当 D 运转时,先挪用 countDownLatch.await() 查抄计数器值能否为 0,若没有为 0 则连结等候形态;当A B C 各自运转完后城市操纵countDownLatch.countDown(),将倒计数器加 1,当三个皆运转完后,计数器被加至 0;此时立刻触收 D 的 await() 运转完毕,持续背下施行。

因而,CountDownLatch 合用于一个线程来等候多个线程的状况。

三个活动员各自筹办,比及三小我私家皆筹办好后,再一同跑

上里是一个形象的比方,针对 线程 A B C 各自开端筹办,曲到三者皆筹办终了,然后再同时运转 。也便是要完成一种 线程之间相互等候 的结果,那该当怎样去完成呢?

上里的 CountDownLatch 能够雍么倒计数,但当计数终了,只要一个线程的 await() 会获得呼应,没法让多个线程同时触收。

为了完成线程间相互等候这类需供,我们能够操纵 CyclicBarrier 数据构造,它的根本用法是:

  • 先创立一个大众 CyclicBarrier 工具,设置 同时等候 当边程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

  • 那些线程同时开端本人做筹办,本身筹办终了后,需求等候他人筹办终了,这时候挪用 cyclicBarrier.await(); 便可开端等候他人;

  • 当指定的 同时等候 当边程数皆挪用了 cyclicBarrier.await();时,意味着那些线程皆筹办终了好,然后那些线程才 同时持续施行。


完成代码以下,假想有三个跑步活动员,各自筹办好后等候其别人,局部筹办好后才开端跑:

  1. private static void runABCWhenAllReady() {  
  2.     int runner = 3;  
  3.     CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);  
  4.     final Random random = new Random();  
  5.     for (char runnerName='A'; runnerName <= 'C'; runnerName++) {  
  6.         final String rN = String.valueOf(runnerName);  
  7.         new Thread(new Runnable() {  
  8.             @Override  
  9.             public void run() {  
  10.                 long prepareTime = random.nextInt(10000) + 100;  
  11.                 System.out.println(rN + " is preparing for time: " + prepareTime);  
  12.                 try {  
  13.                     Thread.sleep(prepareTime);  
  14.                 } catch (Exception e) {  
  15.                     e.printStackTrace();  
  16.                 }  
  17.                 try {  
  18.                     System.out.println(rN + " is prepared, waiting for others");  
  19.                     cyclicBarrier.await(); // 当前活动员筹办终了,等候他人筹办好  
  20.                 } catch (InterruptedException e) {  
  21.                     e.printStackTrace();  
  22.                 } catch (BrokenBarrierException e) {  
  23.                     e.printStackTrace();  
  24.                 }  
  25.                 System.out.println(rN + " starts running"); // 一切活动员皆筹办好了,一同开端跑  
  26.             }  
  27.         }).start();  
  28.     }  
  29. }  
赶钙代码

挨印的成果以下:

A is preparing for time: 4131
B is preparing for time: 6349
C 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 running
A starts running
B starts running

子线程完成某件使命后,把获得的成果回传给主线程

实践的开辟中,我们常常要创立子线程去做一些耗时使命,然后把使命施行成果回传给主线程利用,这类状况正在 Java 里要怎样完成呢?

回忆线程的创立,我们普通会把 Runnable 工具传给 Thread 来施行。Runnable界说以下:

  1. public interface Runnable {  
  2.     public abstract void run();  
  3. }  
赶钙代码

能够看到 run() 正在施行完后没有会返回任何成果。那假如期望返回成果呢?那里能够操纵另外一个相似的接心类 Callable:

  1. @FunctionalInterface  
  2. public interface Callable<V> {  
  3.     /**  
  4.      * Computes a result, or throws an exception if unable to do so.  
  5.      *  
  6.      * @return computed result  
  7.      * @throws Exception if unable to compute a result  
  8.      */  
  9.     V call() throws Exception;  
  10. }  
赶钙代码

能够看出 Callable 最年夜区分便识痰回范型 V 成果。

那末现位个成绩便是,怎样把子线程的成果回传返来呢?正在 Java 里,有一个类是共同 Callable 利用的:FutureTask,不外留意,它获得成果的 get 办法会壅闭主线程。

举例,我们念让子线程来计较从 1 减到 100,并把算出的成果返回到主线程。

  1. private static void doTaskWithResultInWorker() {  
  2.     Callable<Integer> callable = new Callable<Integer>() {  
  3.         @Override  
  4.         public Integer call() throws Exception {  
  5.             System.out.println("Task starts");  
  6.             Thread.sleep(1000);  
  7.             int result = 0;  
  8.             for (int i=0; i<=100; i++) {  
  9.                 result += i;  
  10.             }  
  11.             System.out.println("Task finished and return result");  
  12.             return result;  
  13.         }  
  14.     };  
  15.     FutureTask<Integer> futureTask = new FutureTask<>(callable);  
  16.     new Thread(futureTask).start();  
  17.     try {  
  18.         System.out.println("Before futureTask.get()");  
  19.         System.out.println("Result: " + futureTask.get());  
  20.         System.out.println("After futureTask.get()");  
  21.     } catch (InterruptedException e) {  
  22.         e.printStackTrace();  
  23.     } catch (ExecutionException e) {  
  24.         e.printStackTrace();  
  25.     }  
  26. }  
赶钙代码

挨印成果以下:

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

能够看到,主线程挪用 futureTask.get() 办法时壅闭主线程;然后 Callable 内部开端施行,并返回运算成果;此时 futureTask.get() 获得成果,主线程规复运转。

那里我们能够教到,经由过程 FutureTask 战 Callable 能够间接正在主线程得到子线程的运算成果,只不外需求壅闭主线程。固然,假如没有期望壅闭主线程,能够思索操纵 ExecutorService,把 FutureTask 放到线程池来办理施行。

小结

多线程是当代言语的配合特征,而线程间通讯、线程同步、线程宁静是很主要的话题。本文┞冯对 Java 当边程间通讯停止了大抵的解说,后绝借会对线程同步、线程宁静停止解说。





举报 使用道具

回复
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

1

粉丝

308

主题
精彩推荐
热门资讯
网友晒图
图文推荐

Archiver|手机版|java学习基地 |网站地图

GMT+8, 2021-6-25 11:47 , Processed in 0.477508 second(s), 27 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.