Java中实现线程间通信的实例教程_第1页
Java中实现线程间通信的实例教程_第2页
Java中实现线程间通信的实例教程_第3页
Java中实现线程间通信的实例教程_第4页
Java中实现线程间通信的实例教程_第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第Java中实现线程间通信的实例教程2.如何让两个线程按照指定的方式有序相交?

3.线程D在A、B、C都同步执行完毕后执行

4.三个运动员分开准备同时开跑

5.子线程将结果返回给主线程

总结

前言

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

关于线程间通信本文涉及到的方法和类包括:thread.join()、object.wait()、object.notify()、CountdownLatch、CyclicBarrier、FutureTask、Callable。

接下来将用几个例子来介绍如何在Java中实现线程间通信:

如何让两个线程依次执行,即一个线程等待另一个线程执行完成后再执行?

如何让两个线程以指定的方式有序相交执行?

有四个线程:A、B、C、D,如何实现D在A、B、C都同步执行完毕后执行?

三个运动员分开准备,然后在每个人准备好后同时开始跑步。

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

1.如何让两个线程依次执行?

假设有两个线程:A和B,这两个线程都可以按照顺序打印数字,代码如下:

publicclassTest01{

publicstaticvoidmain(String[]args)throwsInterruptedException{

demo1();

publicstaticvoiddemo1(){

Threada=newThread(()-{

printNumber("A");

Threadb=newThread(()-{

printNumber("B");

a.start();

b.start();

publicstaticvoidprintNumber(StringthreadName){

inti=0;

while(i++3){

try{

Thread.sleep(100);

}catch(InterruptedExceptione){

e.printStackTrace();

System.out.println(threadName+"print:"+i);

得到的结果如下:

Aprint:1

Bprint:1

Bprint:2

Aprint:2

Aprint:3

Bprint:3

可以看到A和B同时打印数字,如果我们希望B在A执行完成之后开始执行,那么可以使用thread.join()方法实现,代码如下:

publicstaticvoiddemo2(){

Threada=newThread(()-{

printNumber("A");

Threadb=newThread(()-{

System.out.println("B等待A执行");

try{

a.join();

}catch(InterruptedExceptione){

e.printStackTrace();

printNumber("B");

a.start();

b.start();

得到的结果如下:

B等待A执行

Aprint:1

Aprint:2

Aprint:3

Bprint:1

Bprint:2

Bprint:3

我们可以看到该a.join()方法会让B等待A完成打印。

thread.join()方法的作用就是阻塞当前线程,等待调用join()方法的线程执行完毕后再执行后面的代码。

查看join()方法的源码,内部是调用了join(0),如下:

publicfinalvoidjoin()throwsInterruptedException{

join(0);

查看join(0)的源码如下:

//注意这里使用了sychronized加锁,锁对象是线程的实例对象

publicfinalsynchronizedvoidjoin(longmillis)throwsInterruptedException{

longbase=System.currentTimeMillis();

longnow=0;

if(millis0){

thrownewIllegalArgumentException("timeoutvalueisnegative");

//调用join(0)执行下面的代码

if(millis==0){

//这里使用while循环的目的是为了避免虚假唤醒

//如果当前线程存活则调用wait(0),0表示永久等待,直到调用notifyAll()或者notify()方法

//当线程结束的时候会调用notifyAll()方法

while(isAlive()){

wait(0);

}else{

while(isAlive()){

longdelay=millis-now;

if(delay=0){

break;

wait(delay);

now=System.currentTimeMillis()-base;

从源码中可以看出join(longmillis)方法是通过wait(longtimeout)(Object提供的方法)方法实现的,调用wait方法之前,当前线程必须获得对象的锁,所以此join方法使用了synchronized加锁,锁对象是线程的实例对象。其中wait(0)方法会让当前线程阻塞等待,直到另一个线程调用此对象的notify()或者notifyAll()方法才会继续执行。当调用join方法的线程结束的时候会调用notifyAll()方法,所以join()方法可以实现一个线程等待另一个调用join()的线程结束后再执行。

虚假唤醒:一个线程在没有被通知、中断、超时的情况下被唤醒;

虚假唤醒可能导致条件不成立的情况下执行代码,破坏被锁保护的约束关系;

为什么使用while循环来避免虚假唤醒:

在if块中使用wait方法,是非常危险的,因为一旦线程被唤醒,并得到锁,就不会再判断if条件而执行if语句块外的代码,所以建议凡是先要做条件判断,再wait的地方,都使用while循环来做,循环会在等待之前和之后对条件进行测试。

2.如何让两个线程按照指定的方式有序相交?

如果现在我们希望B线程在A线程打印1后立即打印1,2,3,然后A线程继续打印2,3,那么我们需要更细粒度的锁来控制执行顺序。

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

publicstaticvoiddemo3(){

Objectlock=newObject();

ThreadA=newThread(()-{

synchronized(lock){

System.out.println("A1");

try{

lock.wait();

}catch(InterruptedExceptione){

e.printStackTrace();

System.out.println("A2");

System.out.println("A3");

ThreadB=newThread(()-{

synchronized(lock){

System.out.println("B1");

System.out.println("B2");

System.out.println("B3");

lock.notify();

A.start();

B.start();

得到的结果如下:

A1

B1

B2

B3

A2

A3

上述代码的执行流程如下:

首先我们创建一个由A和B共享的对象锁:lock=newObject();

当A拿到锁时,先打印1,然后调用lock.wait()方法进入等待状态,然后交出锁的控制权;

B不会被执行,直到A调用该lock.wait()方法释放控制权并且B获得锁;

B拿到锁后打印1,2,3,然后调用lock.notify()方法唤醒正在等待的A;

A唤醒后继续打印剩余的2,3。

为了便于理解,我将上面的代码添加了日志,代码如下:

publicstaticvoiddemo3(){

Objectlock=newObject();

ThreadA=newThread(()-{

System.out.println("INFO:A等待获取锁");

synchronized(lock){

System.out.println("INFO:A获取到锁");

System.out.println("A1");

try{

System.out.println("INFO:A进入waiting状态,放弃锁的控制权");

lock.wait();

}catch(InterruptedExceptione){

e.printStackTrace();

System.out.println("INFO:A被B唤醒继续执行");

System.out.println("A2");

System.out.println("A3");

ThreadB=newThread(()-{

System.out.println("INFO:B等待获取锁");

synchronized(lock){

System.out.println("INFO:B获取到锁");

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

得到的结果如下:

INFO:A等待获取锁

INFO:A获取到锁

A1

INFO:A进入waiting状态,放弃锁的控制权

INFO:B等待获取锁

INFO:B获取到锁

B1

B2

B3

INFO:B执行结束,调用notify方法唤醒A

INFO:A被B唤醒继续执行

A2

A3

3.线程D在A、B、C都同步执行完毕后执行

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

我们要实现的目标是:A、B、C三个线程可以同时开始运行,各自独立运行完成后通知D;D不会开始运行,直到A、B和C都运行完毕。所以我们CountdownLatch用来实现这种类型的通信。它的基本用法是:

创建一个计数器,并设置一个初始值,CountdownLatchcountDownLatch=newCountDownLatch(3);

调用countDownLatch.await()进入等待状态,直到计数值变为0;

在其他线程调用countDownLatch.countDown(),该方法会将计数值减一;

当计数器的值变为0时,countDownLatch.await()等待线程中的方法会继续执行下面的代码。

实现代码如下:

publicstaticvoidrunDAfterABC(){

intcount=3;

CountDownLatchcountDownLatch=newCountDownLatch(count);

newThread(()-{

System.out.println("INFO:D等待ABC运行完成");

try{

countDownLatch.await();

System.out.println("INFO:ABC运行完成,D开始运行");

System.out.println("Disworking");

}catch(InterruptedExceptione){

e.printStackTrace();

}).start();

for(charthreadName='A';threadName='C';threadName++){

finalStringname=String.valueOf(threadName);

newThread(()-{

System.out.println(name+"isworking");

try{

Thread.sleep(100);

}catch(InterruptedExceptione){

e.printStackTrace();

System.out.println(name+"finished");

countDownLatch.countDown();

}).start();

得到的结果如下:

INFO:D等待ABC运行完成

Aisworking

Bisworking

Cisworking

Cfinished

Bfinished

Afinished

INFO:ABC运行完成,D开始运行

Disworking

其实CountDownLatch它本身就是一个倒数计数器,我们把初始的count值设置为3。D运行的时候,首先调用该countDownLatch.await()方法检查计数器的值是否为0,如果不是0则保持等待状态.A、B、C运行完毕后,分别使用countDownLatch.countDown()方法将倒数计数器减1。计数器将减为0,然后通知await()方法结束等待,D开始继续执行。

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

4.三个运动员分开准备同时开跑

这一次,A、B、C这三个线程都需要分别准备,等三个线程都准备好后开始同时运行,我们应该如何做到这一点?

CountDownLatch可以用来计数,但完成计数的时候,只有一个线程的一个await()方法会得到响应,所以多线程不能在同一时间被触发。为了达到线程相互等待的效果,我们可以使用该CyclicBarrier,其基本用法为:

首先创建一个公共对象CyclicBarrier,并设置同时等待的线程数,CyclicBarriercyclicBarrier=newCyclicBarrier(3);

这些线程同时开始准备,准备好后,需要等待别人准备好,所以调用cyclicBarrier.await()方法等待别人;

当指定的需要同时等待的线程都调用了该cyclicBarrier.await()方法时,意味着这些线程准备好了,那么这些线程就会开始同时继续执行。

想象一下有三个跑步者需要同时开始跑步,所以他们需要等待其他人都准备好,实现代码如下:

publicstaticvoidrunABCWhenAllReady(){

intcount=3;

CyclicBarriercyclicBarrier=newCyclicBarrier(count);

Randomrandom=newRandom();

for(charthreadName='A';threadName='C';threadName++){

finalStringname=String.valueOf(threadName);

newThread(()-{

intprepareTime=random.nextInt(10000);

System.out.println(name+"准备时间:"+prepareTime);

try{

Thread.sleep(prepareTime);

}catch(InterruptedExceptione){

e.printStackTrace();

System.out.println(name+"准备好了,等待其他人");

try{

cyclicBarrier.await();

}catch(InterruptedException|BrokenBarrierExceptione){

e.printStackTrace();

System.out.println(name+"开始跑步");

}).start();

得到结果如下:

A准备时间:1085

B准备时间:7729

C准备时间:8444

A准备好了,等待其他人

B准备好了,等待其他人

C准备好了,等待其他人

C开始跑步

A开始跑步

B开始跑步

CyclicBarrier的作用就是等待多个线程同时执行。

5.子线程将结果返回给主线程

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

一般在创建线程的时候,我们会把Runnable对象传递给Thread执行,Runable的源码如下:

@FunctionalInterface

publicinterfaceRunnable{

publicabstractvoidrun();

可以看到Runable是一个函数式接口,该接口中的run方法没有返回值,那么如果要返回结果,可以使用另一个类似的接口Callable。

函数式接口:只有一个方法的接口

Callable接口的源码如下:

@FunctionalInterface

publicinterfaceCallableV{

*Computesaresult,orthrowsanexceptionifunabletodoso.

*@returncomputedresult

*@throwsExceptionifunabletocomputearesult

Vcall()throwsException;

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

那么接下来的问题是,如何将子线程的结果传回去呢?Java有一个类,FutureTask,它可以与一起工作Callable,但请注意,get用于获取结果的方法会阻塞主线程。FutureTask本质上还是一个Runnable,所以可以直接传到Thread中。

比如我们想让子线程计算1

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论