java 跨线程调用_[译]如何用Java实现跨线程通信

正常来说每一个子线程只需要完成它自己的任务,但有些时候,我们也许需要多个线程一起来完成一件任务,这就需要线程间通信

这篇文章涉及到的类和方法有:thread.join(), object.wait(), object.notify(), CountdownLatch, CyclicBarrier, FutureTask, Callable……

文章中涉及到的代码在这里

我会用一系列的例子来说明如何用在Java中实现线程间通信 如何让两个线程顺序执行 如何让两个线程用指定的方式有序交叉执行 有四个线程:A、B、C、D(D不会执行直到A、B、C已经执行完成并且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(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

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

}

}

然后我们会得到这样的结果:

B print: 1

A 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 starts waiting for A");

try {

A.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

printNumber("B");

}

});

B.start();

A.start();

}

现在获得的结果是:

B starts waiting for A

A print: 1

A print: 2

A print: 3

B print: 1

B print: 2

B print: 3

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

如何让两个线程用有指定的方式有序交叉执行

所以如果现我们希望B在A打印了1后开始打印1,2,3,然后A继续打印2、3会怎么样呢?显然的,我们需要更细粒度的锁来控制执行顺序。

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

/*** A 1, B 1, B 2, B 3, A 2, A 3*/

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

A waiting…

B 1

B 2

B 3

A 2

A 3

这正是我们想要的

发生了什么?首先创建了在A和B之间共享的锁对象:lock = new Object()

当A得到了锁,A先打印1,然后A调用lock.wait()方法使A将进入等待状态,并且交出锁的控制权

在A调用loack.wait()方法释放控制权然后B得到锁之前,B不会执行

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

A在唤醒后继续打印剩下的2、3

我在下面的代码里添加了一些log使其更容易理解

private static void demo3() {

Object lock = new Object();

Thread A = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("INFO: A is waiting for the lock");

synchronized (lock) {

System.out.println("INFO: A got the lock");

System.out.println("A 1");

try {

System.out.println("INFO: A is ready to enter the wait state, giving up control of the lock");

lock.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("INFO: B wakes up A, and A regains the lock");

System.out.println("A 2");

System.out.println("A 3");

}

}

});

Thread B = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("INFO: B is waiting for the lock");

synchronized (lock) {

System.out.println("INFO: B got the lock");

System.out.println("B 1");

System.out.println("B 2");

System.out.println("B 3");

System.out.println("INFO: B ends printing, and calling the notify method");

lock.notify();

}

}

});

A.start();

B.start();

结果如下:

INFO: A is waiting for the lock

INFO: A got the lock

A 1

INFO: A is ready to enter the wait state, giving up control of the lock

INFO: B is waiting for the lock

INFO: B got the lock

B 1

B 2

B 3

INFO: B ends printing, and calling the notify method

INFO: B wakes up A, and A regains the lock

A 2

A 3

D在A、B、C全都顺序执行完后才被执行

早先介绍过的thread.join()方法允许一个线程在等待另一个执行完毕后继续执行。但是如果我们在D中依次join A、B、C,会让A、B、C依次执行,但是我们希望这三个线程同步执行译者补充:原文中这一段有点问题,要实现“D在A、B、C全都顺序执行完后才被执行”这个目标,使用join是没有任何问题的,只需要在D执行任务之前join A、B、C就好了。关于countDownLatch之于join的意义有二: 1. 很多时候我们根本没法调用A.join,比如想要join的线程在线程池中执行,这时就只能使用CountdownLatch 2. CountdownLatch可以在线程A执行了一半后就调用countDown()方法,让想要执行的线程得到执行。join就做不到这一点,因为join只能等被join的线程完全执行完毕后,再执行想要执行的线程

我们想要实现的目标是:A、B、C这三个线程能在同一时间start开始执行,并且他们会在各自执行完毕后notify通知D,直到A、B、C都执行完毕之前,D不会执行。所以我们使用CountdownLatch来实现这种类型的通信创建一个计数器并且设置一个初始值,CountdownLatch countDownLatch = new CountDownLatch(3)

在等待线程中调用countDownLatch.await()方法,线程进入wait状态直到计数器的值变为0

在其他线程中调用countDownLatch.countDown()方法,这个方法会导致计数器的值减一

当其他线程调用countDown()方法使计数器到0,等待线程的countDownLatch.await()方法会立刻退出,并且继续执行剩下的代码

实现的代码如下:

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

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,它将会保持在wait状态。A、B、C在分别执行完成后将会调用countDownLatch.countDown()方法时计数器减一。当三个线程全部执行完毕后,计数器将被减少到0。然后await()方法将会被触发,D继续执行。

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

三个运动员准备后开跑

三个跑步者分别准备,当他们都准备好后,在同一时间开跑

这次,A、B、C三个线程需要分别准备,他们都准备好后,同时start。我们应该怎么实现呢?

上面提到的CountDownLatch可以用来倒计时,但是当计数完成后只有一个线程的await会得到响应,所以多个线程不能在同一时间得到触发

为了实现多个线程等待彼此的效果,我们可以使用CyclicBarrier数据结构,基本的使用方式是: 1. 首先创造public的CyclicBarrier对象,同时设置等待的线程数。CyclicBarrier cyclicBarrier = new CyclicBarrier(3) 2. 三个线程同时开始准备,一个线程准备完成后,他需要等待别人准备完成,所以调用cyclicBarrier.await()方法等待其他线程 3. 一旦当指定的线程们全部调用了cyclicBarrier.await()方法,这就意味着所有的线程都准备好了,接下来这些线程同时开始继续执行

实现的代码如下。想象他们是三个跑者需要同时开始跑步,所以他们需要等待直到其他人准备好

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(); // The current runner is ready, waiting for others to be ready } catch (InterruptedException e) {

e.printStackTrace();

} catch (BrokenBarrierException e) {

e.printStackTrace();

}

System.out.println(rN + "starts running"); // All the runners are ready to start running together }

}).start();

}

}

结果如下:

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对象给线程执行。Runnable的定义如下:

public interface Runnable {

public abstract void run();

}

你可以看到run()方法执行后没有返回任何结果。那如果你想返回结果呢?这里你可以看见一个类似的接口Callable:

@FunctionalInterface

public interface Callable {

/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/

V call() throws Exception;

}

可以看到最大的不同是Callable返回泛型结果

所以下一个问题是,如何传递子线程的返回值呢?Java有一个类,FutureTask,可以和Callable一同工作,但是请注意用来得到返回值的get()方法会阻塞主线程

举个例子,我们希望子线程计算1到100的和然后返回结果到主线程

private static void doTaskWithResultInWorker() {

Callable callable = new Callable() {

@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 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到线程池来管理执行

总结

多线程是现代语言的常见功能,线程间通信,线程同步和线程安全是非常重要的主题。

译者补充

关于wait和notify

前面有提到wait和notify,他们是用来进行线程间的协作 必须要获取到某个对象的锁才能调用,否则会抛出异常 obj.wait()后,线程会进入锁对象的等待队列 notify后,会从队列中随机唤醒一个线程 notifyAll唤醒所有wait的线程

wait和sleep的区别

先说下两者的共同点:都会阻塞当前线程。 不同点: wait会交出调用wait对象的对象锁。而且wait存在notify方法来唤醒调用wait的线程,这个是sleep没有的。 sleep不会交出任何已获得的对象锁:The thread does not lose ownership of any monitors Thread(Java Platform SE 7 )

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值