場景介紹
在實際的工作過程中,為了減少用戶的等待時間,通常會使用多線程去并行處理相關(guān)任務。主任務線程等待其他并行任務處理完成后,獲取執(zhí)行的結(jié)果,經(jīng)過相關(guān)處理,返回給用戶。這種方式是多線程在程序中主要的使用方式,因此這就要求主線程必須等待任務線程的執(zhí)行,然后匯總結(jié)果。
實現(xiàn)方法
join方法
在 CountDownLatch 類未出現(xiàn)之前要實現(xiàn)主線程等待只能使用 join 方法(這樣說有點絕對,因為 Future 的 get 方法也能實現(xiàn)閉鎖的功能,但是需要自己將其緩存后再去循環(huán)處理,不是使用 JDK 所提供的方法了)。join 方法和之前介紹的方法不同,它是線程 Thread 類的方法,它沒有入?yún)⒁矝]有返回值。
示例代碼
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
Thread t1 = new Thread(() -> {
System.out.println("t1 start");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end");
});
Thread t2 = new Thread(() -> {
System.out.println("t2 start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 end");
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("main end");
}
如上代碼中,創(chuàng)建了兩個子線程 t1 和 t2, 然后分別啟動兩個子線程并調(diào)用各自的 join 方法。方法執(zhí)行后,會先執(zhí)行兩個子線程,執(zhí)行完后才會執(zhí)行主線程的打印任務。
執(zhí)行過程為,當主線程執(zhí)行到 t1.join() 的時候其會被阻塞,等待 t1 執(zhí)行完后再執(zhí)行 t2.join() 會再次被阻塞,等到 t2 執(zhí)行完后,再執(zhí)行主線程的打印任務。
需要注意的是,要先啟動后再調(diào)用 join 方法才會有用,如果先調(diào)用 join 方法再啟動是不會生效的。
CountDownLatch
上面我們介紹了通過使用線程類的 join 方法來讓主線程等待,但是這個方法不夠靈活,對實際工作的各種場景并不能完全滿足,因此 JDK 提供了 CountDownLatch 類,可以讓我們能更好的實現(xiàn)該功能。
示例代碼
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
try {
System.out.println("t1 start");
Thread.sleep(500);
System.out.println("t1 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start();
new Thread(() -> {
try {
System.out.println("t2 start");
Thread.sleep(1000);
System.out.println("t2 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start();
countDownLatch.await();
System.out.println("main end");
}
}
如上代碼中,創(chuàng)建了一個 CountDownLatch 對象,由于有兩個子線程,所以構(gòu)造函數(shù)傳入的參數(shù)為2。然后創(chuàng)建了兩個子線程,在各自的 finally 模塊中調(diào)用 CountDownLatch 對象的 countDown 方法;分別啟動兩個子線程,最后由主線程調(diào)用 CountDownLatch 對象的 await 方法,并執(zhí)行打印任務。
代碼的執(zhí)行過程為,在創(chuàng)建 CountDownLatch 對象時,構(gòu)造方法傳入了計數(shù)器數(shù)量2,主線程調(diào)用 CountDownLatch 對象的 await 方法時將會被阻塞,直到 CountDownLatch 對象中的計數(shù)器變?yōu)?,主線程才會返回,阻塞也就結(jié)束了。在子線程執(zhí)行時,由于調(diào)用了 CountDownLatch 對象的 countDown 方法,每次調(diào)用計數(shù)器都會減1 ,兩次調(diào)用后計數(shù)器變?yōu)?,主線程結(jié)束阻塞打印日志。
需要注意的是,CountDownLatch 對象的 await 方法被返回時,只需要 CountDownLatch 對象的計數(shù)器變?yōu)?即可,所以只需要調(diào)用 countDown 方法的次數(shù)為計數(shù)器的數(shù)量即可,并不需要各線程都執(zhí)行完成,示例代碼如下:
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
try {
System.out.println("t1 start");
Thread.sleep(2000);
System.out.println("t1 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start();
new Thread(() -> {
try {
System.out.println("t2 start");
Thread.sleep(1000);
System.out.println("t2 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
countDownLatch.countDown();
}
}).start();
countDownLatch.await();
System.out.println("main end");
}
總結(jié)
1、join 方法是 Thread 類的方法,調(diào)用它時主線程會阻塞到調(diào)用它的線程執(zhí)行完后才執(zhí)行。
2、CountDownLatch 類是 JDK 專門提供的一個用來操作線程等待的類,它是使用計數(shù)器的方法,并不關(guān)心各線程是否執(zhí)行完成,因此它更加的靈活。
3、由于在實際工作中,常常使用線程池來管理程序中的線程,因此使用 CountDownLatch 類來處理會更加靈活實用。