正常情况下,每个子线程完成各自的任务就可以结束了,但是有时候,需要多个线程协同工作来完成任务,这个时候就涉及到线程间的通讯了。
本文涉及知识点:
- thread.join(),
- object.wait(),
- object.notify(),
- CountdownLatch,
- CyclicBarrier,
- FutureTask,
- Callable 。
下面将用几个例子作为切入点来讲解Java里有那些方法来实现线程通信。
1,如何让两个线程依次执行
2,如何让两个线程按照指定的方式交叉运行
3,四个线程A,B,C,D,其中D要等待A B C全部执行后才能执行,并且A B C是同步运行的
4,三个运动员各自准备,等到3个人都准备好之后,在一起比赛
5,子线程完成某个任务后,把得到的结果回传给主线程。
如何让两个线程依次执行
假设有两个线程,一个是线程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 开始等待 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: 3B 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()两个方法来实现,代码如下:
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
这正是我们需要的结果,那么在这个过程中发生了什么呢?
1,首先,创建一个A和B共享的对象所lock = new Object();
2,当A得到锁之后,先打印1,然后调用了lock.wait()方法,交出了锁的控制权,进入wait状态
3,对于B而言,由于A最开始获取了锁,导致B无法运行,直到A调用了lock.wait()之后释放了锁的控制权,B才得到了锁
4,B在得到锁之后打印1,2,3,然后调用lock.notify()方法,欢迎正在wait的A
5,A被唤醒后,继续打印剩下的2,3
四个线程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来实现这种通信方式,他的基本用法:
1,创建一个计算器,设置初始值,CountDownLatch cdl = new CountDownLatch(3);
2,在线程里调用cdl.await()方法,进入等到状态,直到计算值变成0
3,在其他线程里,调用cdl.countDown()方法,该方法会将计数值减1
4,当其他线程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 workingA finished
C finished
B finished
其实,简单点来说,CountDownLatch就是一个倒数计数器,我们把初始数值设置为3,当D运行时明会先调用countDownLatch.await()检查计数器值是否是0,如果不为0则继续保持等待状态,当A B C各自都完成后都会调用countDown()方法将倒数计数器减1,当三个都运行后,计数器被减成0,此时立即出发D的await(),运行结束,继续向下推进。
三个运动员各自准备,等待三个人都准备好之后,在一起跑
上面是一个形象的比喻,针对线程A B C各自开始准备,直到三者都准备完毕,然后在同时运行,也就是要实现一种线程之间互相等待的结果,那应该如何进行实现?
上面的countDownLatch可以用来倒计数,但当计数完毕,只有一个线程await()会得到相应,无法让多个线程同时触发。
为了实现这种需求,可以使用CyclicBarrier数据结构,他的基本用法:
1,先创建一个公共的CyclicBarrier,设置同时等待的线程数,CyclicBarrier cb = new CyclicBarrier(3);
2,这些线程同时开始自己准备,自身准备完毕之后,需要等待别人准备完毕,这时调用cb.await(),即可开始等待别人
3,当指定的“同时等待的线程数”都调用了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(); // 当前运动员准备完毕,等待别人准备好
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(rN + " starts running"); // 所有运动员都准备好了,一起开始跑
}
}).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
子线程完成后,把得到的结果传递给主线程
实际开发过程中,我们经常要创建一些子线程来执行一些比较耗时的任务,然后把任务的执行结果返回给主线程,这种情况下如何实现?
一般创建线程,都是把Runnable对象传递给Thread对象去执行,Runnable定义如下:
public interface Runnable {
public abstract void run();
}
我们看到run方法在执行完毕之后不会有任何的返回结果,如果我们想要得到返回结果的话,可以使用Callable接口
@FunctionalInterface
public interface Callable<V> {
/**
* 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有一个返回结果(泛型V)
在java中有一个类是配合Callable使用的:FuntureTask,不过注意,他获取结果的时候会阻塞主线程
例如:我们想要让子线程执行计算1-100的和,并把返回的结果返回给主线程
private static void doTaskWithResultInWorker() {
Callable<Integer> callable = new Callable<Integer>() {
@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<Integer> 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()
可以看到,主线程调用funtureTask.get()会阻塞主线程,然后在Callable内部执行,并返回运算结果,此时funtureTask.get()得到结果,主线程恢复运行。
如果不希望阻塞主线程,可以考虑ExecutorService,把FuntureTask放到线程池去管理执行即可。