假設(shè)有一個場景:生產(chǎn)汽車分為了三步:制造車身、制造輪子沟饥、組裝車身和輪子添怔。單線程下我們的代碼:
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class CarDemo {
@Test
public void madeCarTest1(){
StopWatch stopWatch=new StopWatch();
stopWatch.start();
int n=5;
for(int i =0;i<n;i++){
//單線程
log.info("開始制造");
String body=madeBody();
log.info(body);
String wheels=madeWheels();
log.info(wheels);
String car=madeCar(body,wheels);
log.info(car);
}
stopWatch.stop();
log.info("制造"+n+"臺汽車耗時"+stopWatch.getTotalTimeSeconds()+"秒");
}
private String madeBody(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "車身";
}
private String madeWheels(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "輪子";
}
private String madeCar(String body,String wheels){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return body+wheels;
}
}
單線程下,車身贤旷、輪子和組裝都是穿行的广料,耗時15秒。
咱們改成多線程:
private String body;
private String wheels;
@Test
public void madeCarTest2() throws InterruptedException {
//多線程
StopWatch stopWatch=new StopWatch();
stopWatch.start();
int n=5;
for(int i =0;i<n;i++){
Thread t1=new Thread(()->{
body=madeBody();
log.info(body);
});
Thread t2=new Thread(()->{
wheels=madeWheels();
log.info(wheels);
});
t1.start();
t2.start();
t1.join();
t2.join();
String car=madeCar(body,wheels);
log.info(car);
}
stopWatch.stop();
log.info("制造"+n+"臺汽車耗時"+stopWatch.getTotalTimeSeconds()+"秒");
}
private String madeBody(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "車身";
}
private String madeWheels(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "輪子";
}
private String madeCar(String body,String wheels){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return body+wheels;
}
改成多線程以后幼驶,制造車身和制造輪子都可以并行進(jìn)行了艾杏,耗時10s 提升了30%。
但是這里有個小缺陷盅藻,我們的線程都是new的购桑,重復(fù)的創(chuàng)建線程太耗資源了,我們應(yīng)該用線程池對線程重復(fù)利用氏淑。但是一旦改成線程池勃蜘,線程就不會真正結(jié)束,所以join()方法就失效了假残。java為我們提供了解決方法:CountDownLatch缭贡。它的原理也很簡單,就是有一個計數(shù)器守问,countDown()方法計數(shù)匀归,await可以讓線程等待,直到計數(shù)器次數(shù)達(dá)到目標(biāo)值:
Executor executor= Executors.newFixedThreadPool(2);//創(chuàng)建固定線程數(shù)為2的線程池
@Test
public void madeCarTest3() throws InterruptedException {
//多線程
StopWatch stopWatch=new StopWatch();
stopWatch.start();
int n=5;
for(int i =0;i<n;i++){
CountDownLatch countDownLatch=new CountDownLatch(2);//創(chuàng)建一個大小為2的計數(shù)器
executor.execute(()->{
body=madeBody();
log.info(body);
countDownLatch.countDown();
});
executor.execute(()->{
wheels=madeWheels();
log.info(wheels);
countDownLatch.countDown();
});
countDownLatch.await();
String car=madeCar(body,wheels);
log.info(car);
}
stopWatch.stop();
log.info("制造"+n+"臺汽車耗時"+stopWatch.getTotalTimeSeconds()+"秒");
}
private String madeBody(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "車身";
}
private String madeWheels(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "輪子";
}
private String madeCar(String body,String wheels){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return body+wheels;
}
可以看到耗帕,比原來快了0.01秒多穆端。
這個程序已經(jīng)是最優(yōu)了的嗎?不仿便,還可以繼續(xù)優(yōu)化体啰。因?yàn)閙adeCar()的時候攒巍,我們還可以繼續(xù)制造下一個的輪子和車身荒勇。你使用CountDownLatch也可以完成這部分工作柒莉,但是java為我們提供更方便的CyclicBarrier沽翔,CyclicBarrier 在達(dá)到期望數(shù)值的時候,回調(diào)一個方法仅偎,并且把數(shù)值重置為初始值:
Vector<String> bodys=new Vector<String>();
Vector<String> wheelss=new Vector<String>();
ExecutorService executor1= Executors.newFixedThreadPool(3);//創(chuàng)建固定線程數(shù)為2的線程池
CyclicBarrier barrier=new CyclicBarrier(3,()->{
executor1.execute(()->{
String body=bodys.remove(0);//拿第一個body
String wheels=wheelss.remove(0);//拿第一個wheels
String car=madeCar(body,wheels);
log.info(car);
});
});//創(chuàng)建一個計數(shù)器橘沥,當(dāng)數(shù)值達(dá)到2的時候,調(diào)一次
@Test
public void madeCarTest4() throws InterruptedException {
//多線程
StopWatch stopWatch=new StopWatch();
stopWatch.start();
int n=5;
for(int i =0;i<n;i++){
executor1.execute(()->{
body=madeBody();
bodys.add(body);
log.info(body);
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
executor1.execute(()->{
wheels=madeWheels();
wheelss.add(wheels);
log.info(wheels);
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
try {
barrier.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
executor1.shutdown();//關(guān)閉線程池痢艺,但是會等線程執(zhí)行完畢
boolean stat=executor1.awaitTermination(2,TimeUnit.HOURS);//掛起線程介陶,直到線程池關(guān)閉
if(stat){
log.info("所有線程執(zhí)行完畢");
}else {
log.info("超時或者被中斷");
}
stopWatch.stop();
log.info("制造"+n+"臺汽車耗時"+stopWatch.getTotalTimeSeconds()+"秒");
}
private String madeBody(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "車身";
}
private String madeWheels(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "輪子";
}
private String madeCar(String body,String wheels){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return body+wheels;
}
我們改成了這種類似于 生產(chǎn)-消費(fèi) 模式后,速度更快了植酥。因?yàn)樵诮M裝車子的時候弦牡,下一個車子的車身和輪子已經(jīng)開始制造了。
最后驾锰,上面的代碼其實(shí)有個問題:CyclicBarrier 的回調(diào)方法其實(shí)不應(yīng)該跟其他公用一個線程池,應(yīng)該單獨(dú)使用一個長度為1的線程池椭豫。大家想想為什么?
下一章 java并發(fā)編程 - 6 - 并發(fā)容器