Java 如何实现线程间通信
正常情况下,每个子线程完成各自的任务就可以结束了。不过有的时候,我们希望多个线程协同工作来完成某个任务,这个时候就涉及到了线程间的通信
涉及到的内容主要有:
- thread.join()
- object.wait()
- objet.notify()
- CountdownLatch
- CyclicBarrier
- FutureTask
- Callable
从下面几个例子作为切入点说明Java 有哪些方法实现线程间通信
- 如何两个线程依次执行
- 如何让两个线程按照指定的方式有序交叉运行
- 四个线程A B C D,其中D要等到A B C 全执行完后才执行,而且 A B C是同步运行的
- 三个运动员各自准备,等到三个人都准备好后,再一起跑
- 子线程完成某件任务后,把得到的结果回传给主线程
如何让两个线程依次执行?
假设有两个线程,一个线程 A, 另外一个线程 B,两个线程分别依次打印1-3三个数字即可,代码实现如下:
public class ThreadDemo1
{
/**
*
*/
public ThreadDemo1()
{
// TODO Auto-generated constructor stub
}
public static void demo1() {
Thread A =new Thread(new Runnable() {
public void run() {
printNumber("A");
}
});
Thread B=new Thread(new Runnable() {
public void run() {
printNumber("B");
}
});
A.start();
B.start();
}
protected static void printNumber( String threadName )
{
// TODO Auto-generated method stub
int i=0;
while(i++<3) {
try {
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName+" print: "+i);
}
}
/**
* @param args
*/
public static void main( String[] args )
{
// TODO Auto-generated method stub
demo1();
}
}
运行结果如下:
A print: 1
B print: 1
B print: 2
A print: 2
B print: 3
A print: 3
从运行结果可以看到线程 A和线程 B是同时打印的。
那么,如果我们希望 B在A全部打印完成后再开始打印呢?我们可以利用thread.join()方法实现,代码如下:
package com.test.thread;
public class ThreadJoinDemo {
public ThreadJoinDemo() {
// TODO Auto-generated constructor stub
}
public static void demo1() {
Thread A = new Thread(new Runnable() {
public void run() {
printNumber("A");
}
});
Thread B = new Thread(new Runnable() {
public void run() {
System.out.println("B 开始等待 A");
try {
A.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
printNumber("B");
}
});
A.start();
B.start();
}
protected static void printNumber(String threadName) {
// TODO Auto-generated method stub
int i = 0;
while (i++ < 3) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " print: " + i);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
demo1();
}
}
运行结果如下:
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()两个方法来实现,代码实现如下:
package com.test.thread;
public class ThreadDemo3 {
public ThreadDemo3() {
// TODO Auto-generated constructor stub
}
private static void demo3() {
Object lock=new Object();
Thread A=new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(lock) {
System.out.println("A1");
try {
lock.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A2");
System.out.println("A3");
}
});
Thread B=new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(lock) {
System.out.println("B1");
System.out.println("B2");
System.out.println("B3");
lock.notify();
}
}
});
A.start();
B.start();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
demo3();
}
}
运行结果如下:
A1
B1
B2
B3
A2
A3
从运行结果看正是我们想要的结果,那么这个过程发生了什么,先分析如下:
- 首先创建一个A和B共享的对象锁lock=new Object();
- 当A得到锁后,先打印,然后调用lock.wait()方法,交出锁的控制权,进入wait状态
- 对于线程B而言,由于A最开始获得锁,导致线程B无法运行,直到A调用lock.wait()方法释放控制权后,线程B才能得到锁
- 线程B在获得锁后打印1,2,3,然后调用lock.notify()方法,唤醒正在wait的A
- 线程A被唤醒后,继续打印剩余的2,3
为了更好的理解,我们现在修改上面的代码加上相关的日志方便理想,代码如下:
package com.test.thread;
public class ThreadDemo3 {
public ThreadDemo3() {
// TODO Auto-generated constructor stub
}
private static void demo3() {
Object lock=new Object();
Thread A=new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("INFO: A 等待锁");
synchronized(lock) {
System.out.println("INFO: A 得到锁 lock");
System.out.println("A1");
try {
System.out.println("INFO: A 准备进入等待状态,放弃锁");
lock.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("INFO: 有人唤醒了A,A重新获得锁 lock");
System.out.println("A2");
System.out.println("A3");
}
});
Thread B=new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("INFO: B 等待锁");
synchronized(lock) {
System.out.println("INFO: B 获得了锁 lock");
System.out.println("B1");
System.out.println("B2");
System.out.println("B3");
System.out.println("INFO: B 打印完毕,调用notify方法唤醒A");
lock.notify();
}
}
});
A.start();
B.start();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
demo3();
}
}
运行结果如下:
INFO: A 等待锁
INFO: A 得到锁 lock
INFO: B 等待锁
A1
INFO: A 准备进入等待状态,放弃锁
INFO: B 获得了锁 lock
B1
B2
B3
INFO: B 打印完毕,调用notify方法唤醒A
INFO: 有人唤醒了A,A重新获得锁 lock
A2
A3
四个线程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 来实现这类通信方式,它的基本用法是:
- 创建一个计数器,设置初始值,CountdownLatch countDownLatch=new CountdownLatch(2);
- 在等待线程里面调用countDownLatch.await()方法,进入等待状态,直到计数器数据变成0;
- 在其他线程里面,调用countDownLatch.countDown()方法,该方法会将计数器数值减小1;
- 当其他线程的countDown()方法把计数器数值变成0时,等待线程里面的countDownLatch.await()立即退出,继续执行下面的代码
代码实现如下:
package com.test.thread;
import java.util.concurrent.CountDownLatch;
public class ThreadCountDownLatch {
public ThreadCountDownLatch() {
// TODO Auto-generated constructor stub
}
private static void runDAfterABC() {
int worker=3;
CountDownLatch countDownLatch=new CountDownLatch(worker);
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("D is waiting for other thress thread");
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() {
// TODO Auto-generated method stub
System.out.println(tN+" is working");
try {
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(tN+" finished");
countDownLatch.countDown();
}
}).start();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
runDAfterABC();
}
}
运行结果如下:
D is waiting for other thress thread
A is working
B is working
C is working
A finished
B finished
C 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 各自开始准备,直到三个线程都准备完毕,然后再同时运行,也就是要实现一种线程之间想关等待的效果,那么这种情况应该如何实现?
为了实现线程间相互等待这种需求,我们可以利用CyclicBarrier数据结构,它的基本用法如下:
- 先创建一个公共CyclicBarrier对象,设置同时等待的线程数,CyclicBarrier cyclicBarrier=new CyclicBarrier(3);
- 这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用cyclicBarrider.await();即可开始等待别人
- 当指定的同时等待的线程数都调用了cycliBarrier.await()时,意味着这些线程都准备完毕,然后这些线程才同时继续运行
代码实现如下:
package com.test.thread;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public CyclicBarrierDemo() {
// TODO Auto-generated constructor stub
}
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() {
// TODO Auto-generated method stub
long prepareTime=random.nextInt(10000)+100;
System.out.println(rN+" is preparing for time:"+prepareTime);
try {
Thread.sleep(prepareTime);
}catch(InterruptedException 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();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
runABCWhenAllReady();
}
}
运行结果如下:
B is preparing for time:2555
A is preparing for time:8147
C is preparing for time:849
C is prepared waiting for others
B is prepared waiting for others
A is prepared waiting for others
A starts running
C starts running
B starts running
子线程完成某件任务后,把得到的结果回传给主线程
实际开发过程中,我们经常要创建子线程来做一些耗时的任务,然后把任务执行结果回传给主线程使用,这种情况在Java中如何实现?
我们一般会把Runnable对象传给Thread 去执行,可以看到run()在执行完成后不会返回任何结果,那么如果希望返回结果,这里可以利用一个类似的接口Callable来实现,Callable最大的区别就是返回泛型V结果
那么下一个问题就是,如果把子线程的结果回传给主线程?在Java中有一个类是配合Callable使用的:FutureTask,需要注意的是,它获取结果的方法get会阻塞主线程
举例说明,我们想让子线程去计算1加到100,并把结果返回给主线程,代码实现如下:
package com.test.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class FutureTaskDemo {
public FutureTaskDemo() {
// TODO Auto-generated constructor stub
}
private static void doTaskWithResultInWorker() {
Callable<Integer> callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
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<Integer>(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(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
doTaskWithResultInWorker();
}
}
代码运行结果如下:
Before futureTask.get()
Task starts
Task finished and return result
Result:5050
After futureTask.get()
从运行结果可以看到,主线程调用futureTask.get()方法时阻塞主线程;然后Callable内部开始执行,并返回计算结果;此时futureTask.get()得到结果,主线程恢复运行
通过上述代码我们可以学到,通过FutureTask和Callable可以直接在主线程里面获得子线程的运算结果,只不过需要阻塞主线程,当然,如果不希望阻塞主线程,可以考虑利用ExecutorService,把FutureTask放到线程池去管理执行
posted on 2018-11-08 15:40 anqli_java 阅读(314) 评论(0) 编辑 收藏 举报