Java 用兩個線程交替打印數字和字母
前一段時間聽馬士兵老師講課,講到某公司的一個面試,兩個線程,其中一個線程輸出ABC,另一個線程輸出123,如何控制兩個線程交叉輸出1A2B3C,由於本人多線程掌握的一直不是很好,所以聽完這道題,個人感覺收獲良多,這是一個學習筆記。這道題有多種解法,不過有些屬於純炫技,所以隻記錄常見的三種解法。首先看第一種
1. park 和 unpark
package cn.bridgeli.demo; import com.google.common.collect.Lists; import java.util.List; import java.util.concurrent.locks.LockSupport; /** * @author BridgeLi * @date 2021/2/6 16:14 */ public class Thread_Communication_Park_Unpark { static Thread t1 = null; static Thread t2 = null; public static void main(String[] args) { final List<Integer> integers = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7); final List<String> strings = Lists.newArrayList("A", "B", "C", "D", "E", "F", "G"); t1 = new Thread(() -> integers.forEach(item -> { System.out.print(item); LockSupport.unpark(t2); LockSupport.park(); }), "t1"); t2 = new Thread(() -> strings.forEach(item -> { LockSupport.park(); System.out.print(item); LockSupport.unpark(t1); }), "t2"); t1.start(); t2.start(); } }
這個是最簡單的實現方法,LockSupport.park() 使當前線程阻塞,而 LockSupport.unpark() 則表示喚醒一個線程,所以他需要一個參數,表示你要喚醒哪個線程,很好理解,也比較簡單。
2. synchronized、notify、wait
package cn.bridgeli.demo; import com.google.common.collect.Lists; import java.util.List; /** * @author BridgeLi * @date 2021/2/6 16:14 */ public class Thread_Communication_Notify_Wait { public static void main(String[] args) { final Object o = new Object(); final List<Integer> integers = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7); final List<String> strings = Lists.newArrayList("A", "B", "C", "D", "E", "F", "G"); new Thread(() -> { synchronized (o) { integers.forEach(item -> { System.out.print(item); o.notify(); try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }); o.notify(); } }, "t1").start(); new Thread(() -> { synchronized (o) { strings.forEach(item -> { System.out.print(item); o.notify(); try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }); o.notify(); } }, "t2").start(); } }
這是一個比較傳統的寫法,也是比較難理解的一個寫法,掌握瞭這種寫法之後,對 synchronized、notify、wait 的認識也會有一個新高度,下面就簡單解析一下這種寫法:
我們都知道 synchronized 是一把鎖,而鎖是什麼?就是一個第三方的互斥的一個資源,所以 synchronized (o),就表示我們對 o 這個對象加鎖,是通過修改 o 的對象頭實現的,也就是兩個線程誰成功修改瞭 o 的對象頭,那麼誰就拿到瞭這把鎖,然後就可以執行裡面的相關邏輯,而沒有成功修改 o 的對象頭的線程,就隻有進入到對象 o 的一個等待隊列,等待被系統調度執行(這是一個比較簡單的不是很準確說法,詳細過程,等我將來再寫一個文章想聊鎖升級的過程);然後就是 o.notify(),剛說過 synchronized (o) 一堆線程爭搶鎖,沒有搶到鎖的線程進入對象 o 的等待隊列,所以 o.notify() 含義就是從對象 o 的等待隊列中隨機叫醒一個線程,然後執行;最後是 o.wait() 的含義,他的含義也很簡單,就是當前線程放到對象 o 的等待隊列中,讓出 CPU。
通過這段描述,所以大傢肯定也可以學習到經常遇到的三個問題是怎麼回事:1. wait 是否占用 CPU 資源,因為進入瞭等待隊列,所以是不會占用的;2. 既然 notify、wait 是讓喚醒線程和讓線程進入等待的,為什麼不是 Thread 類的方法,反而是 Object 的方法,因為 notify、wait 是配合 synchronized 一起使用的,不一定用在多線程中,他們控制的是 synchronized 鎖定的對象的等待隊列,而 synchronized 鎖定的對象,肯定是一個 Object,所以 notify、wait 比如是 Object 對象的方法;3. 關於 synchronized (o) 括號裡面是一個對象實例、Class 對象、鎖定代碼塊、靜態變量等等區別,隻要明白 synchronized 修改的是什麼,這些區別就一目瞭然瞭,不再贅述。
最後要說明的一個問題是:循環外邊的 o.notify() 必不可少,有些同學寫的時候可能隨手就忘記瞭,或者不知道為什麼需要最後再 notify 一下,其實仔細想一想就可以明白瞭,假設最後執行的是輸出字母的線程,那麼他之前一定是被執行輸出數字的線程喚醒的,而執行輸出數字的這個線程喚醒執行輸出字母的線程之後,自身就進入等待隊列瞭,所以循環結束之後,如果最後執行輸出字母的線程沒有喚醒執行輸出數字的線程的話,那麼執行輸出數字的線程會一直 wait 阻塞在那裡,將等到天荒地來海枯石爛永遠無法結束。
3. Condition
package cn.bridgeli.demo; import com.google.common.collect.Lists; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author BridgeLi * @date 2021/2/6 16:14 */ public class Thread_Communication_Condition { public static void main(String[] args) { final List<Integer> integers = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7); final List<String> strings = Lists.newArrayList("A", "B", "C", "D", "E", "F", "G"); Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); new Thread(() -> { lock.lock(); try { integers.forEach(item -> { System.out.print(item); condition2.signal(); try { condition1.await(); } catch (InterruptedException e) { e.printStackTrace(); } }); condition2.signal(); } finally { lock.unlock(); } }, "t1").start(); new Thread(() -> { lock.lock(); try { strings.forEach(item -> { System.out.print(item); condition1.signal(); try { condition2.await(); } catch (InterruptedException e) { e.printStackTrace(); } }); condition1.signal(); } finally { lock.unlock(); } }, "t2").start(); } }
當我們理解瞭上面兩種寫法之後,那麼最後這個寫法其實也比較容易理解瞭,就不用我多贅言瞭。
如果有幸有同學看到瞭這裡,那麼我再提出一個小問題,可以思考一下怎麼解決,後面兩種寫法,我們保證是執行輸出數字的線程還是執行輸出字母的線程先執行,也就是先輸出數字或者字母嗎?如果不能的話,現在業務需求要求必須是先輸出字母或者數字怎麼做?(提示:CAS 自旋)
以上就是Java 用兩個線程交替打印數字和字母的詳細內容,更多關於Java 線程交替打印的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- None Found