一、實現方法總覽
- 使用線程的 join 方法
- 使用主線程的 join 方法
- 使用線程的 wait 方法
- 使用線程的線程池方法
- 使用線程的 Condition(條件變量) 方法
- 使用線程的 CuDownLatch(倒計數) 方法
- 使用 CyclicBarrier (迴環柵欄)實現線程按順序執行
- 使用線程的 Sephmore(信號量) 實現線程按順序執行
二、具體實現
我們下面需要完成這樣一個應用場景:
1.早上測試人員、產品經理、開發人員陸續的來公司上班;
2.產品經理規劃新需求;
3.開發人員開發新需求功能;
4.測試人員測試新功能。
規劃需求,開發需求新功能,測試新功能是一個有順序的,我們把thread1看做產品經理,thread2看做開發人員,thread3看做測試人員。
1. 使用線程的 join 方法
join(): 是Theard的方法,作用是調用線程需等待該join()線程執行完成後,才能繼續用下運行。
應用場景: 當一個線程必須等待另一個線程執行完畢才能執行時可以使用join方法。
/**
* 通過子程序join使線程按順序執行
*/
public class ThreadJoinDemo {
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("產品經理規劃新需求");
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread1.join();
System.out.println("開發人員開發新需求功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread2.join();
System.out.println("測試人員測試新功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("測試人員來上班了...");
thread3.start();
System.out.println("產品經理來上班了...");
thread1.start();
System.out.println("開發人員來上班了...");
thread2.start();
}
}
運行結果如下:
早上:測試人員來上班了…
產品經理來上班了…
開發人員來上班了…
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
2. 使用主線程的 join 方法
先在主線程中使用join()來實現對線程的阻塞。
/**
* 通過主程序join使線程按順序執行
*/
public class ThreadMainJoinDemo {
public static void main(String[] args) throws Exception {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("產品經理正在規劃新需求...");
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("開發人員開發新需求功能");
}
});
final Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("測試人員測試新功能");
}
});
System.out.println("早上:");
System.out.println("產品經理來上班了");
System.out.println("測試人員來上班了");
System.out.println("開發人員來上班了");
thread1.start();
//在父進程調用子進程的join()方法後,父進程需要等待子進程運行完再繼續運行。
System.out.println("開發人員和測試人員休息會...");
thread1.join();
System.out.println("產品經理新需求規劃完成!");
thread2.start();
System.out.println("測試人員休息會...");
thread2.join();
thread3.start();
}
}
運行結果如下:
早上:
產品經理來上班了
測試人員來上班了
開發人員來上班了
開發人員和測試人員休息會...
產品經理正在規劃新需求...
產品經理新需求規劃完成!
測試人員休息會...
開發人員開發新需求功能
測試人員測試新功能
3. 使用線程的 wait 方法
wait(): 是Object的方法,作用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)
notify()和notifyAll(): 是Object的方法,作用則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有的線程。
wait(long timeout): 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的notify()方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
應用場景: Java實現生產者消費者的方式。
public class ThreadWaitDemo {
private static Object myLock1 = new Object();
private static Object myLock2 = new Object();
/**
* 爲什麼要加這兩個標識狀態?
* 如果沒有狀態標識,當t1已經運行完了t2才運行,t2在等待t1喚醒導致t2永遠處於等待狀態
*/
private static Boolean t1Run = false;
private static Boolean t2Run = false;
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (myLock1){
System.out.println("產品經理規劃新需求...");
t1Run = true;
myLock1.notify();
}
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (myLock1){
try {
if(!t1Run){
// System.out.println("開發人員先休息會...");
myLock1.wait();
}
synchronized (myLock2){
System.out.println("開發人員開發新需求功能");
myLock2.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (myLock2){
try {
if(!t2Run){
// System.out.println("測試人員先休息會...");
myLock2.wait();
}
System.out.println("測試人員測試新功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
System.out.println("早上:");
System.out.println("測試人員來上班了...");
thread3.start();
System.out.println("產品經理來上班了...");
thread1.start();
System.out.println("開發人員來上班了...");
thread2.start();
}
}
運行結果如下:
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求...
開發人員開發新需求功能
測試人員測試新功能
4. 使用線程的線程池方法
JAVA通過Executors提供了四種線程池
單線程化線程池(newSingleThreadExecutor);
可控最大併發數線程池(newFixedThreadPool);
可回收緩存線程池(newCachedThreadPool);
支持定時與週期性任務的線程池(newScheduledThreadPool)。
單線程化線程池(newSingleThreadExecutor): 優點,串行執行所有任務。
submit(): 提交任務。
shutdown(): 方法用來關閉線程池,拒絕新任務。
應用場景: 串行執行所有任務。如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
static ExecutorService executorService = Executors.newSingleThreadExecutor();
public static void main(String[] args) throws Exception {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("產品經理規劃新需求");
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("開發人員開發新需求功能");
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("測試人員測試新功能");
}
});
System.out.println("早上:");
System.out.println("產品經理來上班了");
System.out.println("測試人員來上班了");
System.out.println("開發人員來上班了");
// System.out.println("首先,產品經理規劃新需求...");
executorService.submit(thread1);
// System.out.println("然後,開發人員開發新需求功能...");
executorService.submit(thread2);
// System.out.println("最後,測試人員測試新功能...");
executorService.submit(thread3);
executorService.shutdown();
}
}
運行結果:
早上:
產品經理來上班了
測試人員來上班了
開發人員來上班了
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
5. 使用線程的 Condition(條件變量) 方法
Condition(條件變量): 通常與一個鎖關聯。需要在多個Contidion中共享一個鎖時,可以傳遞一個Lock/RLock實例給構造方法,否則它將自己生成一個RLock實例。
Condition中await()方法類似於Object類中的wait()方法。
Condition中await(long time,TimeUnit unit)方法類似於Object類中的wait(long time)方法。
Condition中signal()方法類似於Object類中的notify()方法。
Condition中signalAll()方法類似於Object類中的notifyAll()方法。
應用場景: Condition是一個多線程間協調通信的工具類,使得某個,或者某些線程一起等待某個條件(Condition),只有當該條件具備( signal 或者 signalAll方法被調用)時 ,這些等待線程纔會被喚醒,從而重新爭奪鎖。
/**
* 使用Condition(條件變量)實現線程按順序運行
*/
public class ThreadConditionDemo {
private static Lock lock = new ReentrantLock();
private static Condition condition1 = lock.newCondition();
private static Condition condition2 = lock.newCondition();
/**
* 爲什麼要加這兩個標識狀態?
* 如果沒有狀態標識,當t1已經運行完了t2才運行,t2在等待t1喚醒導致t2永遠處於等待狀態
*/
private static Boolean t1Run = false;
private static Boolean t2Run = false;
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println("產品經理規劃新需求");
t1Run = true;
condition1.signal();
lock.unlock();
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
if(!t1Run){
System.out.println("開發人員先休息會...");
condition1.await();
}
System.out.println("開發人員開發新需求功能");
t2Run = true;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
if(!t2Run){
System.out.println("測試人員先休息會...");
condition2.await();
}
System.out.println("測試人員測試新功能");
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("測試人員來上班了...");
thread3.start();
System.out.println("產品經理來上班了...");
thread1.start();
System.out.println("開發人員來上班了...");
thread2.start();
}
}
運行結果:
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
測試人員先休息會...
開發人員開發新需求功能
測試人員測試新功能
6. 使用線程的 CuDownLatch(倒計數) 方法
CountDownLatch: 位於java.util.concurrent包下,利用它可以實現類似計數器的功能。
應用場景: 比如有一個任務C,它要等待其他任務A,B執行完畢之後才能執行,此時就可以利用CountDownLatch來實現這種功能了。
import java.util.concurrent.CountDownLatch;
/**
* 通過CountDownLatch(倒計數)使線程按順序執行
*/
public class ThreadCountDownLatchDemo {
/**
* 用於判斷線程一是否執行,倒計時設置爲1,執行後減1
*/
private static CountDownLatch c1 = new CountDownLatch(1);
/**
* 用於判斷線程二是否執行,倒計時設置爲1,執行後減1
*/
private static CountDownLatch c2 = new CountDownLatch(1);
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("產品經理規劃新需求");
//對c1倒計時-1
c1.countDown();
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//等待c1倒計時,計時爲0則往下運行
c1.await();
System.out.println("開發人員開發新需求功能");
//對c2倒計時-1
c2.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
//等待c2倒計時,計時爲0則往下運行
c2.await();
System.out.println("測試人員測試新功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("測試人員來上班了...");
thread3.start();
System.out.println("產品經理來上班了...");
thread1.start();
System.out.println("開發人員來上班了...");
thread2.start();
}
}
運行結果:
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
7. 使用 CyclicBarrier (迴環柵欄)實現線程按順序執行
CyclicBarrier(迴環柵欄): 通過它可以實現讓一組線程等待至某個狀態之後再全部同時執行。叫做迴環是因爲當所有等待線程都被釋放以後,CyclicBarrier可以被重用。我們暫且把這個狀態就叫做barrier,當調用await()方法之後,線程就處於barrier了。
應用場景: 公司組織春遊,等待所有的員工到達集合地點才能出發,每個人到達後進入barrier狀態。都到達後,喚起大家一起出發去旅行。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 使用CyclicBarrier(迴環柵欄)實現線程按順序運行
*/
public class CyclicBarrierDemo {
static CyclicBarrier barrier1 = new CyclicBarrier(2);
static CyclicBarrier barrier2 = new CyclicBarrier(2);
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("產品經理規劃新需求");
//放開柵欄1
barrier1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
//放開柵欄1
barrier1.await();
System.out.println("開發人員開發新需求功能");
//放開柵欄2
barrier2.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
final Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
//放開柵欄2
barrier2.await();
System.out.println("測試人員測試新功能");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("測試人員來上班了...");
thread3.start();
System.out.println("產品經理來上班了...");
thread1.start();
System.out.println("開發人員來上班了...");
thread2.start();
}
}
運行結果:
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
8. 使用線程的 Sephmore(信號量) 實現線程按順序執行
Sephmore(信號量): Semaphore是一個計數信號量,從概念上將,Semaphore包含一組許可證,如果有需要的話,每個acquire()方法都會阻塞,直到獲取一個可用的許可證,每個release()方法都會釋放持有許可證的線程,並且歸還Semaphore一個可用的許可證。然而,實際上並沒有真實的許可證對象供線程使用,Semaphore只是對可用的數量進行管理維護。
acquire(): 當前線程嘗試去阻塞的獲取1個許可證,此過程是阻塞的,當前線程獲取了1個可用的許可證,則會停止等待,繼續執行。
release(): 當前線程釋放1個可用的許可證。
應用場景: Semaphore可以用來做流量分流,特別是對公共資源有限的場景,比如數據庫連接。假設有這個的需求,讀取幾萬個文件的數據到數據庫中,由於文件讀取是IO密集型任務,可以啓動幾十個線程併發讀取,但是數據庫連接數只有10個,這時就必須控制最多隻有10個線程能夠拿到數據庫連接進行操作。這個時候,就可以使用Semaphore做流量控制。
import java.util.concurrent.Semaphore;
/**
* 使用Sephmore(信號量)實現線程按順序運行
*/
public class SemaphoreDemo {
private static Semaphore semaphore1 = new Semaphore(1);
private static Semaphore semaphore2 = new Semaphore(1);
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("產品經理規劃新需求");
semaphore1.release();
}
});
final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore1.acquire();
System.out.println("開發人員開發新需求功能");
semaphore2.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore2.acquire();
thread2.join();
semaphore2.release();
System.out.println("測試人員測試新功能");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("早上:");
System.out.println("測試人員來上班了...");
thread3.start();
System.out.println("產品經理來上班了...");
thread1.start();
System.out.println("開發人員來上班了...");
thread2.start();
}
}
運行結果:
早上:
測試人員來上班了...
產品經理來上班了...
開發人員來上班了...
產品經理規劃新需求
開發人員開發新需求功能
測試人員測試新功能
三、總結
根據開發需求場景,選擇合適的方法,達到事半功倍的效果。
知識還是要活學活用纔會理解的更深刻,當然,也只有對知識理解的夠深刻才能夠活學活用。