今天又分析了下公司网站搜索的代码。代码里用了不少的线程池(ExecutorService)。当时赶工写的代码里为了进行线程同步,每次要用线程池的时候都新建了线程池,圧入Runnable代码,shutdown()然后等线程池isTerminated() 。这样线程同步是很有效,不过每次处理搜索请求的时候都要建数个线程池、数十个线程,不能重复利用,确实是增加的系统开销。今天查阅了JAVA API文档,发现了java.util.concurrent 包下的CountDownLatch类和CyclicBarrier类可以用来进行线程同步。这下线程池可以不用shutdown而且可以一直被复用了。利用CountDownLatch类实现了一个线程同步的小Demo,效果还不错,这里拿来与诸君共勉。
代码可从敝人github下载https://github.com/DowenLiu126/syncDemo
Demo代码模拟了生活场景:早上起床后到上班后一系列日常。
敝人喜欢早上在上卫生间的时候玩会平板、刷下网页,然后煮两鸡蛋,洗下昨天换下的衣服,吃了早饭再骑自行车上班。如果每件事都使用一个线程表示,这显然是一个多线程并发过程,并且需要线程同步控制(应该极少有人会上厕所和吃饭同时进行。。。。。。)
代码中声明了一个代码日常的类Daily:
static abstract class Daily implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
public Daily(CountDownLatch startSignal, CountDownLatch doneSignal) {
super();
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
@Override
public void run() {
try {
if (this.startSignal != null)
this.startSignal.await();
this.doRun();
this.doneSignal.countDown();
} catch (InterruptedException e) {
}
}
protected abstract void doRun();
}
Daily日常活动类中声明了两个CountDownLatch同步类类型属性startSignal和doneSignal,前者表示此活动开始需要具备的条件,后者表示此活动完成时形成的影响。CountDownLatch类对象初始化时必须指定一个count值,其每调用一次countDown()方法,count值就会减小1。在一个CountDownLatch对象的count值变为0或更小之前,执行此对象的await()方法的线程会一直阻塞。Daily中实现了Runnable接口可以被ExecutorService执行,其run()方法中使用模板模式,执行业务代码前先待开始信号this.startSignal.await(); 之后消减结束信号 this.doneSignal.countDown();
每天第一件事不需要开始信号,所以有
CountDownLatch toiletDoneSignal = new CountDownLatch(2); // 两件事要做,呃,厕所里...
Daily toilet = new Daily(null, toiletDoneSignal) {
@Overrideprotected void doRun() {
System.out.println("起床上厕所...");
try {
Thread.sleep(3000 + r.nextInt(1000)); // 三秒神速。。。意思到了就行吧!
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("上完厕所,神清气爽!");
}
};
Daily news = new Daily(null, toiletDoneSignal) {
@Overrideprotected void doRun() {
System.out.println("拿出平板看新闻...");
try {
Thread.sleep(2000 + r.nextInt(500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("嗯,今天没有飞机掉下来,和平和平!");
}
};
前一组活动具有相同的结束信号,且后继活动需要前一组活动结束,即前一组的结束信号是后继活动的开始信号
CountDownLatch beforeBreakfastDoneSingal = new CountDownLatch(3); // 吃饭之前有3件事要做,起码先做饭吧。
Daily cook = new Daily(toiletDoneSignal, beforeBreakfastDoneSingal) {
@Override
protected void doRun() {
synchronized (lockMe) { // 得看着锅,不能去洗衣服了
System.out.println("今天煮白水面,可怜人啊!");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("煮好了,可怜吧吧的盛出来。");
}
}
};
经过线程同步,各活动的前后顺序就被限制了。执行,输出结果如下
起床上厕所...
拿出平板看新闻...
嗯,今天没有飞机掉下来,和平和平!
上完厕所,神清气爽!
洗刷刷洗刷刷洗...
嘿嘿,我有小熊煮蛋器,每天都有煮蛋吃!
鸡蛋煮好了,凉着!
嗯,洗的很干净!
今天煮白水面,可怜人啊!
煮好了,可怜吧吧的盛出来。
能量填充中!
多谢款待!
上班赶路,嘿咻嘿咻!!!
到了公司,写了这个代码...
代码中为了能更贴近实际,对洗衣服和做饭两件事做了互斥处理,因为没办法一边看着锅一边洗衣服啊(敝人是没办法同时干这两件事的)。关于synchronized关键字的使用,请自行百度或@一下自己的大学老师(小心被K),我就不赘述了。