正常情况下,每个子线程完成各自的任务就可以结束了。不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了。
本文涉及到的知识点:thread.join()
, object.wait()
, object.notify()
, CountdownLatch
, CyclicBarrier
, FutureTask
, Callable
等。
下面我从几个例子作为切入点来讲解下 Java 里有哪些方法来实现线程间通信。
1、如何让两个线程依次执行?
2、那如何让 两
个线程按照指定方式有序交叉运行呢?
3、四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的
4、三个运动员各自准备,等到三个人都准备好后,再一起跑
5、子线程完成某件任务后,把得到的结果回传给主线程
如何让两个线程依次执行?
假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们来看下代码:
/** * 假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。 */ public class Test1 { public static void main(String[] args) { Test1.demo1(); } private static void demo1() { Thread A = new Thread(new Runnable() { @Override public void run() { printNumber("A"); } }); Thread B = new Thread(new Runnable() { @Override public void run() { printNumber("B"); } }); A.start(); B.start(); } 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); } } }
这时我们得到的结果是:
Aprint:1 Bprint:1 Aprint:2 Bprint:2 Bprint:3 Aprint:3
可以看到 A 和 B 是同时打印的。
那么,如果我们希望 B 在 A 全部打印
完后再开始打印呢?我们可以利用 thread.join()
方法,代码如下:
/** * B 在 A 全部打印 完后再开始打印 */ private static void demo2() { Thread A = new Thread(new Runnable() { @Override public void run() { printNumber("A"); } }); Thread B = new Thread(new Runnable() { @Override public void run() { System.out.println("B 开始等待 A"); try { A.join(); } catch (InterruptedException e) { e.printStackTrace(); } printNumber("B"); } }); B.start(); A.start(); }
得到的结果如下:
B 开始等待 A Aprint:1 Aprint:2 Aprint:3 Bprint:1 Bprint:2 Bprint:3
所以我们能看到 A.join()
方法会让 B 一直等待直到 A 运行完毕。
那如何让两个线程按照指定方式有序交叉运行呢?
还是上面那个例子,我现在希望 A 在打印完 1
后,再让 B 打印 1
, 2
, 3
,最后再回到 A 继续打印 2
, 3
。这种需求下,显然 Thread.join()
已经不能满足了。我们需要更细粒度的锁来控制执行顺序。
这里,我们可以利用 object.wait()
和 object.notify()
两个方法来实现。代码如下:
/** * 让两个线程按照指定方式有序交叉运行 */ private static void demo3() { Object lock = new Object(); Thread A = new Thread(new Runnable() { @Override public 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() { @Override public 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 1 B 1 B 2 B 3 A 2 A 3
正是我们要的结果。那么,这个过程发生了什么呢?
1、首先创建一个 A 和 B 共享的对象锁 lock = new Object();
2、当 A 得到锁后,先打印 1,然后调用 lock.wait()
方法,交出锁的控制权,进入 wait
状态;
3、对 B 而言,由于 A 最开始得到了锁,导致 B 无法执行;直到 A 调用 lock.wait()
释放控制权后, B 才得到了锁;
4、B 在得到锁后打印 1, 2, 3;然后调用 lock.notify()
方法,唤醒正在 wait
的 A;
5、A 被唤醒后,继续打印剩下的 2,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 三个线程同时运行,各自独立运行完后通知 D;对 D 而言,只要 A B C 都运行完了,D 再开始运行。针对这种情况,我们可以利用 CountdownLatch
来实现这类通信方式。它的基本用法是:
1、创建一个计数器,设置初始值,CountdownLatch countDownLatch = new CountDownLatch(2);
2、在 等待线程
里调用 countDownLatch.await()
方法,进入等待状态,直到计数值变成 0;
3、在 其他线程
里,调用 countDownLatch.countDown()
方法,该方法会将计数值减小 1;
4、当 其他线程
的 countDown()
方法把计数值变成 0 时,等待线程
里的 countDownLatch.await()
立即退出,继续执行下面的代码。
实现代码如下:
/** * 四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的 */ private static void runDAfterABC() { int worker = 3; CountDownLatch countDownLatch = new CountDownLatch(worker); new Thread(new Runnable() { @Override public 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() { @Override public 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 threads Ais working Cis working Bis working Cfinished Bfinished Afinished 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
数据结构,它的基本用法是:
1、先创建一个公共 CyclicBarrier
对象,设置 同时等待
的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
2、这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用 cyclicBarrier.await();
即可开始等待别人;
3、当指定的 同时等待
的线程数都调用了 cyclicBarrier.await();
时,意味着这些线程都准备完毕好,然后这些线程才 同时继续执行
。
实现代码如下,设想有三个跑步运动员,各自准备好后等待其他人,全部准备好后才开始跑:
/** * 线程 A B C 各自开始准备,直到三者都准备完毕,然后再同时运行 */ 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() { @Override public 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(); // 当前运动员准备完毕,等待别人准备好 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println(rN + "starts running"); // 所有运动员都准备好了,一起开始跑 } }).start(); } }
打印的结果如下:
Ais preparing for time:5530 Bis preparing for time:7029 Cis preparing for time:9543 Ais prepared, waiting for others Bis prepared, waiting for others Cis prepared, waiting for others Cstarts running Astarts running Bstarts running
子线程完成某件任务后,把得到的结果回传给主线程
实际的开发中,我们经常要创建子线程来做一些耗时任务,然后把任务执行结果回传给主线程使用,这种情况在 Java 里要如何实现呢?
回顾线程的创建,我们一般会把 Runnable
对象传给 Thread 去执行。Runnable
定义如下:
public interface Runnable { public abstract void run(); }
可以看到 run()
在执行完后不会返回任何结果。那如果希望返回结果呢?这里可以利用另一个类似的接口类 Callable
:
@FunctionalInterface public interface Callable<V> { V call() throws Exception; }
可以看出 Callable
最大区别就是返回范型 V
结果。
那么下一个问题就是,如何把子线程的结果回传回来呢?在 Java 里,有一个类是配合 Callable 使用的:FutureTask
,不过注意,它获取结果的 get
方法会阻塞主线程。
举例,我们想让子线程去计算从 1 加到 100,并把算出的结果返回到主线程。
/** * 子线程完成某件任务后,把得到的结果回传给主线程 */ private static void doTaskWithResultInWorker() { Callable<Integer> callable = new Callable<Integer>() { @Override public 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 starts Task finished and return result Result:5050 After futureTask.get()
可以看到,主线程调用 futureTask.get()
方法时阻塞主线程;然后 Callable
内部开始执行,并返回运算结果;此时 futureTask.get()
得到结果,主线程恢复运行。
这里我们可以学到,通过 FutureTask
和 Callable
可以直接在主线程获得子线程的运算结果,只不过需要阻塞主线程。当然,如果不希望阻塞主线程,可以考虑利用 ExecutorService
,把 FutureTask
放到线程池去管理执行。
小结
原文地址,请点击
感谢 http://wingjay.com 的分享
相关推荐
使用wait()和notify()实现的生产者与消费者模型,可以了解如何使用wait()和notify()进行线程间通信。(上一次上传的代码有一个问题没有考虑到,这次修补了——CSDN没法撤销资源,只能再上传了)
java线程间通信 很有用的,可以用于安卓开发以及其他java的开发
内容概要:Java线程间通信的代码示例 适用人群:Java 使用场景:Java线程间交换数据 目标:通过本资源示例,深刻理解线程间通讯的基本原理和实现方法,解决工作中实际问题。
管道(pipe)流是一种特殊的流,用于在不同线程(threads)间直接传送数据。...通过使用管道,实现不同线程间的通讯。本文在简要介绍管道的基本概念及管道的创建与使用后,并以一个具体的实例pipeapp加以详细说明。
学习线程之间的通信协调关系。 2. 使用 runnable 接口实现按两个不同的时间间隔( 1 秒和 3 秒)在屏幕上显示当前时间。 3.写一个程序,模拟4个售票窗口共同卖100张火车票的程序。 使用继承Thread类方式和实现...
本篇文章主要介绍了浅析Java中如何实现线程之间通信。针对 Java 的线程间通信进行了大致的讲解,有兴趣的可以了解一下
java多线程每个线程挨着打印ABC的4种实现...里面一共有4中实现方式,实现线程间同步和通信问题,有synchronized实现也有ReentrantLock的实现,还有不用任何锁和同步的实现。欢迎大家一起交流,使用更多的方法来实现。
在单线程编程中,我们可以使用轮询的方式来实现,即频繁地判断是否满足保护条件,若不满足则继续判断,若满足则开始执行。但在多线程编程中,这种方式无疑是非常低效的。如果一个线程持续进行无意义的判断而不释放...
主要为大家详细介绍了Java通过wait()和notifyAll()方法实现线程间通信的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
主要介绍了Java管道流实现线程间通信过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
主要介绍了java线程间通信的通俗解释,介绍了线程通信中的几个相关概念,然后分享了线程通信的实现方式及代码示例,具有一定参考价值 ,需要的朋友可以了解下。
主要介绍了Java线程间通信不同步问题,结合实例形式分析了java线程间通信不同步问题的原理并模拟实现了线程间通信不同步情况下的异常输出,需要的朋友可以参考下
本资源致力于向您介绍 Java 并发编程中的线程基础,涵盖了多线程编程的核心概念、线程的创建和管理,以及线程间通信的基本方法。通过深入学习,您将建立扎实的多线程编程基础,能够更好地理解和应用多线程编程。 多...
此外,还探讨了线程间通信、线程优先级、守护线程、线程组、可重入锁、线程局部变量等关键概念和技术。 每个问题都附带了精确而深入的答案解析,涵盖了多线程编程的各个方面。您将了解线程安全的实现、死锁的避免...
本章主要介绍waitO、 notify All()和notify()方法的使用, 使线程间能互相通信, 合作完成任务。 本章还介绍了Thread Local类的使用。 学习完本章, 读者就能在Thread多线程中进行数据的传递了。4.讲解了...
主要介绍了Java编程之多线程死锁与线程间通信简单实现代码,具有一定参考价值,需要的朋友可以了解下。
Java高并发相关知识点包括: 线程:Java多线程的实现方式,包括继承Thread类和实现Runnable接口。 锁:Java中的锁机制,包括...线程间通信:Java中的线程间通信,包括wait()、notify()、notifyAll()等方法。
之前已经说道,JVM提供了synchronized关键字来实现对变量的同步访问以及用wait和notify来实现线程间通信。在jdk1.5以后,JAVA提供了Lock类来实现和synchronized一样的功能,并且还提供了Condition来显示线程间通信。...
主要介绍了Java并发编程线程间通讯实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
为单线程JavaScript实现多线程并发的功能,语意上参考Java实现,提供getState / sleep / join等API,并提供线程间通信的功能,依赖ES6语法,基于Promise和Async函数实现,故需要Babel编译才能运行。JavaScrpt本来...