使用Java多線程編程時經常遇到主線程需要等待子線程執行完成以后才能繼續執行,那么接下來介紹一種簡單的方式使主線程等待。
java.util.concurrent.CountDownLatch
使用countDownLatch.await()方法非常簡單的完成主線程的等待:
public class ThreadWait { public static void main(String[] args) throws InterruptedException { int threadNumber = 10; final CountDownLatch countDownLatch = new CountDownLatch(threadNumber); for (int i = 0; i < threadNumber; i++) { final int threadID = i; new Thread() { public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(String.format("threadID:[%s] finished!!", threadID)); countDownLatch.countDown(); } }.start(); } countDownLatch.await(); System.out.println("main thread finished!!"); } }
CountDownLatch源碼解析:
1.先看看await()方法:
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
主要看parkAndCheckInterrupt()方法,就是是如何將主線程阻塞住的方法:
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); //通過LockSupport.park()方法將線程交給系統阻塞; return Thread.interrupted(); }
2.看看countDown()方法,我們看看最終被countDown調用的unparkSuccessor()方法;
private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); }
我們可以看到最終使用LockSupport.unpark()方法喚醒了主線程。
注:LockSupport類中的park與unpark方法都是使用的unsafe中的native本地方法;
最后我們來看一段最簡單的使用park與unpark方法阻塞喚醒線程代碼:
public static void main(String[] args) { Thread t = new Thread(() -> { System.out.println("阻塞線程1"); LockSupport.park(); System.out.println("線程1執行完啦"); }); t.start(); try { Thread.sleep(2000); System.out.println("喚醒線程1"); LockSupport.unpark(t); Thread.sleep(2000); System.out.println("主線程結束"); } catch (InterruptedException e) { e.printStackTrace(); } }
運行結果:
阻塞線程1
喚醒線程1
線程1執行完啦
主線程結束