最近總是遇到這樣的問題:怎麼控制線程訪問順序?怎麼讓線程B在線程A執行之後再執行?怎麼讓線程A、B、C按順序打印ABCABC? 以上都可以歸結爲線程的順序執行問題,有這麼4種方案:
(1)join():“等你執行結束,我再執行”
(2)singleThreadPool:只有一個線程的線程池,任務乖乖在隊列中等待被執行
(3)wait/notify機制:“兄弟們,醒醒,到你了”
(4)ReentrantLock 的 condition 的 await/signal機制:“那個兄弟,醒醒,到你了”
接下來就這四種方式進行詳細說明
一、Join()
1、簡介:join(),等待線程結束,才能繼執行。
2、本質:讓線程wait在當前線程對象實例上,觀察join的源碼:
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//---------重點-------------
if (millis == 0) {
while (isAlive()) {
wait(0);
}
//--------------------------
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
解讀:阻塞當前線程,直到目標線程運行結束,如果參數是0,意味着無限等待。
3、join實現線程順序執行
(1)方式1:在run()中調用join方法。
public class M1 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> System.out.println("A"));
Thread t2 = new Thread(() -> {
try {
t1.join(); //瞧,線程2等待線程1執行結束,然後再執行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B");});
Thread t3 = new Thread(() -> {
try {
t2.join(); //線程3等待線程2執行結束,然後再執行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C");});
t1.start();
t3.start();
t2.start();
//輸出:A B C
}
}
(2)方式2:在主方法中調用join
public class M2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> System.out.println("A"));
Thread t2 = new Thread(() -> System.out.println("B"));
Thread t3 = new Thread(() -> System.out.println("C"));
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
//輸出:A B C
}
}
二、單線程線程池
1、簡介:只有一個線程的線程池,任務都在等待隊列中排隊,所以它們是一個一個執行的
2、兩種創建方式:
ExecutorService single=new ThreadPoolExecutor(1,1,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
ExecutorService single = Executors.newSingleThreadExecutor();
3、單線程線程池實現順序執行
public class M2 {
public static void main(String[] args){
Thread t5=new Thread(()->System.out.println("A"));
Thread t6=new Thread(()->System.out.println("B"));
Thread t7=new Thread(()->System.out.println("C"));
ExecutorService single=new ThreadPoolExecutor(1,1,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
single.submit(t5);
single.submit(t7);
single.submit(t6);
}
}
三、wait/notify機制
1、簡介:有多種實現方式,這裏介紹通過一個變量的取值來控制線程執行順序。
注意:wait/notify 要求獲得對象的monitor,所以只能在同步方法或同步代碼塊中使用
2、wait/notify實現線程順序執行
- 先創建一個類,類中包含一個變量,flag。我們約定,flag=0時,輸出A,flag=1時,輸出B,flag=2,輸出C
class MyThread {
private static int flag=0;
//str:稍後要打印的值;order:取值範圍[0,1,2],分別代表先後順序
public synchronized void printStr(String str,int order){
while (flag!=order){
try{
this.wait(); //否則釋放鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(str);
if (order<2)
flag++;
else
flag=1;
this.notifyAll(); //喚起其他線程
}
}
- 主方法中調用
public class M3 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread t8 = new Thread(new Runnable() {
@Override
public void run() {
myThread.printStr("A", 0); //第一個,打印A
}
});
Thread t9 = new Thread(new Runnable() {
@Override
public void run() {
myThread.printStr("B", 2); //第3,打印B
}
});
Thread t10 = new Thread(new Runnable() {
@Override
public void run() {
myThread.printStr("C", 1); //第二,打印C
}
});
t8.start();
t9.start();
t10.start();
//執行結果:A C B
}
}
四、condition的await/signal機制
1、簡介:這個機制和wait/notify很像,(筆者狹隘地認爲,condition一定情況下可以更加精確~)
2、Conditon:await/signal要配合ReentrantLock使用,通過ReentrantLock的newConditon方法生成一個與當前重入鎖綁定的Condition實例,讓我們能在線程何時的時間等待,或在一個特定的時刻獲得通知。阻塞隊列之所以能夠阻塞,底層也是利用了condition哦
3、await/signal實現線程順序執行
public class M5{
private static Lock lock=new ReentrantLock();
private static Condition condition1=lock.newCondition();
private static Condition condition2=lock.newCondition();
private static boolean printA=true; //先打印A
private static boolean printB=false; //先打印B
public static void main(String[] args) throws InterruptedException {
Thread t11=new Thread(() -> {
try{
lock.lock();
if (!printA) {
condition1.await(); //如果當前printA=false,則在條件1上等待
}
System.out.println("A");
printB=true;
printA=false;
condition2.signal(); //喚醒在condition2上等待的線程
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
});
Thread t12=new Thread(() -> {
try{
lock.lock();
if (!printB){
condition2.await();
}
System.out.println("B");
printA=true;
printB=false;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
});
t11.start();
t12.start();
}
}
參考資料:
1、《Java高併發程序設計》
2、讓線程按順序執行8種方法:https://www.cnblogs.com/wenjunwei/p/10573289.html