正常来说每一个子线程只需要完成它自己的任务,但有些时候,我们也许需要多个线程一起来完成一件任务,这就需要线程间通信
这篇文章涉及到的类和方法有: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 )