版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433697
线程之间经常需要一定的同步,比如线程A需要线程B的部分运算结果,但又不必等到线程B把所有的结果都算出来,否则A就要待太长时间。
下面这个例子就是这个应用场景,主线程需要等待子线程从数据库中加载记录,但是子线程把所有的记录都加载完要花挺长时间。
而实际上,主线程最开始只需要一条记录就可以继续自己的后续动作了。怎么办呢?下面的代码利用传统的Object.wait()/nofity()方法来实现:
代码语言:javascript复制 public void openSource() {
// 创建一个初值为1的倒数计数器对象作为通知对象
Object notifier=new Object();
// 启动新线程用于长时间的加载任务
new Thread(new Runnable() {
@Override
public void run() {
boolean isNotified=false;
try {
int index = 0;
while (true) {
{
// 加载数据。。。。
}
// 加载第一条记录后,唤醒等待线程
if (1 == index) {
synchronized (notifier){
notifier.notify();
isNotified=true;
}
}
}
} finally {
//循环结束,不论有没有加载到数据记录,都执行唤醒,
//以确保notify无论如何都会被执行一次,否则等待线程会一直阻塞
synchronized (notifier){
if(!isNotified)
notifier.notify();
}
}
}
}).start();
synchronized (notifier){
try {
// 启动子线程后立即阻塞, 等待被子线程唤醒
notifier.wait();
} catch (InterruptedException e) {}
}
// 确保子线程加载了第一记录后,主线程继续自己的工作。。。。
}
话说,用Object.wait()/notify()倒是挺直观,但是,这synchronized同步块代码写起来实在有点啰嗦,我是个一行代码都不想多写的懒人。
就在想这代码能不能 看着更简单点?
于是想到了java.util.concurrent包下的CountDownLatch,
这是个好东西,顾名思义,它实现了一个多线程环境下倒数计数器锁,当计数器倒数到0时,唤醒阻塞的线程,允许多个线程同时对计数器减1,用在这里实在大材小用了。不过管它呢,方便就成啊。
于是前面的代码就被我用CountDownLatch改造成下面这样:
代码语言:javascript复制 public void openSource() {
// 创建一个初值为1的倒数计数器锁
CountDownLatch notifier=new CountDownLatch(1);
new Thread(new Runnable() {
@Override
public void run() {
try {
int index = 0;
while (true) {
{
// 加载数据。。。。
}
if (1 == index) {
// 计数减1,唤醒等待线程
notifier.countDown();
}
}
} finally {
// 线程结束之前再执行一次唤醒动作
notifier.countDown();
}
}
}).start();
try {
// 启动子线程后立即阻塞, 等待被倒数计数器锁唤醒
notifier.await();
} catch (InterruptedException e) {}
}
相比前面的代码,finally{}代码块中没有再判断是否已经执行过唤醒动作,为什么呢,因为countDown当计数已经归0的时候什么也不做,所以就算多执行一次countDown也完全不影响程序的逻辑。
没有烦人的synchronized同步块代码块后,代码是不是看起来简洁一点点呢?
当然相比原始的wait/nofity,CountDownLatch的实现是经过高度封装的代码,最终它也是用wait/nofity来实现的,但逻辑更复杂,所以性能上有多少影响我并不清楚,因为我的应用环境是在程序初始化的时候,并不是高频调用,所以我并不太关注这个。