博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何在Java中实现线程间通信
阅读量:6516 次
发布时间:2019-06-24

本文共 7783 字,大约阅读时间需要 25 分钟。

hot3.png

如何在Java中实现线程间通信

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

将使用几个示例来解释如何在Java中实现线程间通信:

  • 如何使两个线程按照顺序执行?
  • 如何使两个线程以指定的方式有序交叉
  • 有四个线程:A、B、C、D, D在A、B和C同步执行完成后执行
  • 三名运动员分开准备,然后他们在每个运动员准备好后开始跑步
  • 子线程完成任务后,它会将结果返回给主线程

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

假设有两个线程:线程A和线程B,两个线程都可以顺序打印出三个数字(1-3)。

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();}

printNumber(String),用于依次打印1、2、3三个数字

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

我们得到的结果是:

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

可以同时看到A和B打印数字。 那么,如果我们希望B在A打印完毕后开始打印怎么办?可以使用该thread.join()方法,代码如下:

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... 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,等B打印完成,继续打印A的2、3。 可以利用 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---1B---1B---2B---3A---2A---3
  • 首先,我们创建一个由A和B共享的对象锁: Object lock = new Object();
  • 当A获得锁定时,它首先打印1,然后调用 lock.wait();方法使其进入等待状态,然后移交锁定控制
  • 在A调用 lock.wait();释放控制的方法并且B获得锁定之前,B不会被执行
  • B在获得锁定后打印1、2、3,然后调用lock.notify();方法唤醒正在等待的A
  • A被唤醒后继续打印剩余的2、3

修改demo3方法,加入日志添加到上面的代码中,以便更容易理解。

private static void demo3(){	Object lock = new Object();		Thread A = new Thread(new Runnable() {		@Override		public void run() {			System.out.println("A正在等待锁 ");			synchronized (lock) {				System.out.println("A得到锁");				System.out.println("A---1");				try {					System.out.println("A准备进入等待状态,放弃对锁的控制");					lock.wait();				} catch (InterruptedException e) {					e.printStackTrace();				}				System.out.println("B唤醒A,A重新得到锁");				System.out.println("A---2");				System.out.println("A---3");			}		}	});			Thread B = new Thread(new Runnable(){		@Override		public void run() {			System.out.println("B正在等待锁 ");			synchronized (lock) {				System.out.println("B---1");				System.out.println("B---2");				System.out.println("B---3");				System.out.println("B打印结束,准备调用lock.notify()");				lock.notify();			}		}	});		A.start();	B.start();}

结果如下:

A正在等待锁 A得到锁A---1A准备进入等待状态,放弃对锁的控制B正在等待锁 B---1B---2B---3B打印结束,准备调用lock.notify()B唤醒A,A重新得到锁A---2A---3

三、D在A、B和C同步执行完成后执行?

前面介绍thread.join()方法允许一个线程在等待另一个线程完成后继续执行。但是我们如果将A、B、C有序的连接到D线程中,将会依次执行A、B、C,我们是希望它们三个同步允许。 要实现的目标:三个线程A、B、C同时开始运行,每个线程在完成独立运行后通知D。在A、B、C完成之前,D不会执行。所以我们用CountdownLatch来实现这种类型的通信。它的基本用法:

  • 创建一个计数器,并且设置一个初始值,CountDownLatch countDownLatch = new CountDownLatch(3);
  • 在等待线程中调用countDownLatch.wait();,进入等待状态,直到计数值变为0
  • 在其他线程中调用countDownLatch.countDown();,计数值减1
  • countDown()其他线程中的方法将计数值变为0时,countDownLatch.wait();等待线程中的方法将立即退出并且继续执行

代码如下:

private static void demo4(){	int woeker = 3;	CountDownLatch countDownLatch = new CountDownLatch(woeker);		Thread D = new Thread(new Runnable(){		@Override		public void run() {			System.out.println("D正在等待其他三个线程");			try {				countDownLatch.await();				System.out.println("全部完成,D开始执行");			} catch (InterruptedException e) {				e.printStackTrace();			}		}	});		D.start();		for(char threadName='A';threadName<='C';threadName++){		final String tName = String.valueOf(threadName);		new Thread(new Runnable(){			@Override			public void run() {				System.out.println("开始执行线程:"+tName);				try {					Thread.sleep(100);				} catch (InterruptedException e) {					e.printStackTrace();				}				System.out.println(tName+"执行完成");				countDownLatch.countDown();			}		}).start();	} // for(char threadName='A';threadName<='C';threadName++)}

执行结果:

D正在等待其他三个线程开始执行线程:A开始执行线程:C开始执行线程:BC执行完成B执行完成A执行完成全部完成,D开始执行

实际上CountDownLatch本身就是计时器,我们将初始计数值设置为3,当D运行时,它首先会调用countDownLatch.await();来检查计数器值是否为0,如果不为0,它将保持等待状态,A、B、C各自调用countDownLatch.countDown();,在完成后将计数器递减1。当这三个完成时,计数器的值为0,然后D中的countDownLatch.await();被触发,开始执行D。 因此CountDownLatch适用于一个线程需要等待多个线程的情况。

四、3名运动员准备跑步

三名运动员分开准备,然后他们在每个运动员准备好后开始跑步。 三个线程A、B、C中的每一个都需要单独准备,然后他们三个都准备好后,开始同时执行。 在上面说CountDownLatch计时器,完成计数的时候,只有一个线程的一个await()方法会得到响应,所以多线程不能再同一时间被触发。 为了实现多线程相互等待的效果,我们可以使用CyclicBarrier,基本用法:

  • 首先创建一个公共对象CyclicBarrier,并且设置同时等待的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
  • 这些线程开始同时准备,准备好之后,他们需要等待其它线程完成准备,cyclicBarrier.await();等待其他人
  • 当等待指定的线程调用cyclicBarrier.await();时,就说明这些线程已经准备就绪,那么这些线程将开始同时继续执行。

代码如下:

private static void demo5(){	int runner = 3;	CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);	final Random random = new Random();		for(char runnerName = 'A'; runnerName <= 'C'; runnerName++){		final String rName = String.valueOf(runnerName);				new Thread(new Runnable(){			[@Override](https://my.oschina.net/u/1162528)			public void run() {				long prepareTime = random.nextInt(10000) + 100;				System.out.println(rName + ":准备的时间:" + prepareTime);				try {					Thread.sleep(prepareTime);				} catch (InterruptedException e) {					e.printStackTrace();				}				System.out.println(rName + "准备完毕,等待其他");				try {					cyclicBarrier.await();				} catch (InterruptedException | BrokenBarrierException e) {					e.printStackTrace();				}				//所有人都准备完成				System.out.println(rName + "开始执行");			}		}).start();	}}

结果如下:

A:准备的时间:6960C:准备的时间:1079B:准备的时间:2713C准备完毕,等待其他B准备完毕,等待其他A准备完毕,等待其他A开始执行C开始执行B开始执行

五、子线程完成任务后,将结果返回给主线程

在实际开发中,我们经常需要创建子线程来执行一些耗时的任务。然后将执行结果传递给主线程。 在创建线程时,通常我们会使用Runnable

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

其中该run()在执行后不会返回任何结果,如果你想返回结果怎么办? 可以使用Callable

@FunctionalInterfacepublic interface Callable
{ /** * Computes a result, or throws an exception if unable to do so. * * [@return](https://my.oschina.net/u/556800) computed result * [@throws](https://my.oschina.net/throws) Exception if unable to compute a result */ V call() throws Exception;}

如何将子线程的结果传回去? 例如,子线程计算从1到100的总和,并将结果返回给主线程。

private static void demo6(){		Callable
callable = new Callable
(){ [@Override](https://my.oschina.net/u/1162528) public Integer call() throws Exception { System.out.println("任务开始"); Thread.sleep(1000); int result = 0; for(int i=0;i<=100;i++){ result += i; } System.out.println("任务结束并且返回结果"); return result; } }; FutureTask
futureTask = new FutureTask<>(callable); new Thread(futureTask).start(); try { System.out.println("主线程调用 futureTask.get()之前"); System.out.println("Result:" + futureTask.get()); System.out.println("主线程调用 futureTask.get()之后"); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }}

结果如下:

调用 futureTask.get()之前任务开始任务结束并且返回结果Result:5050调用 futureTask.get()之后

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

在这里我们了解到FutureTaskCallable可以直接在主线程中获取子线程的结果,但是他们会阻塞主线程,如果你不想阻止主线程,可以考虑使用ExecutorService线程池来管理执行。

本文参考:

转载于:https://my.oschina.net/lwenhao/blog/3016677

你可能感兴趣的文章
使用eclipse与android studio 在开发自定义控件时的区别
查看>>
我的友情链接
查看>>
mysql学习笔记
查看>>
年年有鱼游戏Android源码项目
查看>>
java使用Iterator、for循环同步数据
查看>>
创建镜像iso文件
查看>>
Linux下创建软RAID5和RAID10实战
查看>>
C++类的存储
查看>>
ActiveReports 报表应用教程 (8)---交互式报表之动态过滤
查看>>
解决使用Handler时Can't create handler inside thread that has not called Looper.prepare()
查看>>
跟我一起学docker(四)--容器的基本操作
查看>>
磁化强度
查看>>
C/C++ 数据范围
查看>>
LVS+keepalived+nginx
查看>>
monkey如何通过uiautomatorviewer的bounds坐标点击控件
查看>>
第22章,mysql数据库-1
查看>>
【亲测】教你如何搭建 MongoDB 复制集 + 选举原理
查看>>
虚拟化网络技术
查看>>
阿里云中间件推出全新开发者服务
查看>>
56.随机产生的id重复问题
查看>>