继上篇中需在远程服务器上修改一个nginx配置文件的内容,需要修改多次,所以采用多线程从而加快修改速度,由于通过开启多线程远程修改nginx服务器配置文件,速度确实提高了很多,但是遇到了一个问题,那就是修改线程并不会马上执行,但是你的保存nginx文件操作却已经返回执行成功,那么就会出现界面已显示修改成功,而其实修改操作正在进行,甚至后面的运行过程中甚至出现异常,这种情况肯定是不符合要求的,当然这种也可以通过消息队列后续继续修改,但是总感觉这样会影响用户体验。经过查找,发现了CountDownLatch这个类,可以让主线程等待其他子线程执行完毕后主线程才开始继续执行,这样便可以在其他子线程全部修改完毕之后在返回修改结果,同时,虽然时间上会比之前稍有增加,但是此时的运行时间也是所有子线程中执行最长的那个子线程的时间,并不会累加每个线程的运行时间,所以相比串行的方式在运行时间还是有了很大的提升。
CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。CountDownLatch的构造器如下:
public CountDownLatch(int count) { }; //参数count为计数值
CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程就可以恢复执行任务,所以在使用CountDownLatch时需要开发人员很明确需要等待的条件,否则很容易造成await()方法一直阻塞的情况。针对上述CountDownLatch的特点,CountDownLatch主要可以有一下两方面的应用:第一种应用就是某一线程在开始运行前需等待n个线程执行完毕。这种情况也正是我在项目中遇到的情况 ,将CountDownLatch的计数器初始化为new CountDownLatch(n)
,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown()
,当计数器的值变为0时,在CountDownLatch上 await()
的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。具体代码的举例如下:
public class ShareniuDriver {
public static void main(String[] args) throws InterruptedException {
//n为子线程数,在使用时,需等待几个就初始化为几个
CountDownLatch countDownLatch = new CountDownLatch(n);
for (int i = 0; i < n; ++i) // create and start threads
{
//开启n个子线程,这里传入countDownLatch是为了执行完后让计数器减一
new Thread(new Worker(countDownLatch,i)).start();
}
//主线程在这里会阻塞,等待所有的子线程执行完毕,也就是计数器为0,主线程继续执行
countDownLatch.await();
doSomethingEls // don't let run yete();
}
private static void doSomethingElse() {
System.out.println("分享牛 我在做其他事情.....");
}
static class Worker implements Runnable {
private CountDownLatch countDownLatch;
private int i;
public Worker( CountDownLatch countDownLatch,int i) {
this.countDownLatch= countDownLatch;
this.i=i;
}
public void run() {
try {
doWork();
} catch (Exception e) {
} finally {
//子线程执行完毕后计数器减一
countDownLatch.countDown();
}
}
private void doWork() {
System.out.println("开始工作....."+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
另一种使用场景就是实现最大的并行性。有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数器为1的CountDownLatch,并让其他所有线程都在这个锁上等待,只需要调用一次countDown()方法就可以让其他所有等待的线程同时恢复执行。
当然,CountDownLatch也存在一些不足的方面,比如CountDownLatch是计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。同时在使用过程中,要保证在任意一个子线程出现异常的情况下计数器也要能够减一,这样主线程才不至于一直阻塞在那里,造成类似于死锁的情况。所以这也只是一种处理办法,读者根据自己情况进行选择使用。
程序之路漫漫,吾将上下而求索