場景一
在我們面試中經常會有這么一個場景,就是我們用線程A輸出“A”字符,有線程B輸出“B”字符,交替進行,要求A線程執行完任務輸出:“A線程打印完了”,B線程執行完任務輸入:“B線程打印完了”,最后有主線程輸出一句話“我打印完了”!
當你看到這個場景時,有點多線程經驗的人肯定會感覺很容易,然后有可能進行下面的實現,上代碼:
//A線程類 public class ThreadA extends Thread { private TestThread testThread; public ThreadA(TestThread testThread){ this.testThread=testThread; } @Override public void run(){ for(int i=0;i<5;i++){ testThread.printStr("A"); } System.out.println("a線程打印完了"); } } //B線程類 public class ThreadB extends Thread { private TestThread testThread; public ThreadB(TestThread testThread){ this.testThread=testThread; } @Override public void run(){ for(int i=0;i<5;i++){ testThread.printStr("B"); } System.out.println("b線程打印完了"); } } //測試類 public class TestThread { public static void main(String[] args){ TestThread testThread=new TestThread(); ThreadA threadA = new ThreadA(testThread); ThreadB threadB = new ThreadB(testThread);
threadA.setName("threadA"); threadB.setName("threadB"); threadB.setPriority(2)//這個目的是將B線程的優先級降低,讓A線程先執行,使得在打印的時候先打印“A”,但是我要說但是,在實際測試中你會發現輸出的結果並不會像我們預想的一樣,具體什么原因造成的我們下面會解釋 threadB.start(); threadA.start(); try { threadA.join(); threadB.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("交替打印完成"); } public synchronized void printStr(String str){ String name = Thread.currentThread().getName(); if("A".equals(str)){ System.out.println(name+"-----"+"A"); }else if("B".equals(str)){ System.out.println(name+"-----"+"B"); } try { notify(); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面的代碼猛一看很完美,但是如果你真正執行過之后你會發現出現下面的結果,“A”和“B”字符是交替打印了,但是我們想要的三句話,只輸出了一句,並且你的idea一直處在運行狀態:
其實如果我們仔細分析代碼就不難發現為什么程序一直處在沒有結束運行的狀態,並且只打印了一句話而不是三句話:再出去最后一個threadB-----B的時候結合代碼你會發現這是線程B會執行nofity喚醒A線程,然后自己進入wait狀態,A線程被喚醒后執行A線程的run方法,但是現在A線程中的run方法的for已經執行晚了,也就是沒法進入for循環體了,所以就直接執行了打印語句,這也就是最后一行打印的原因,而因為A線程沒有進入for循環體,所以沒有調用同步方法printStr,也就沒有辦法調用里面的notify來喚醒B線程,那么B線程也就沒有辦法執行打印語句了,一直處在wait狀態,也就是B線程一致處在沒有結束的狀態,那自然而然主線程也不會輸出打印語句。
還記得我們在上面的代碼中說的一個線程優先級的問題嗎?如果你對這段代碼執行多次的情況你會發現,第一次打印的可不都是“A”字符,還有可能是“B”,這是為什么呢?
其實這主要使我們對線程優先級理解的一個誤區,對於線程優先級我們通常的理解是具有較高優先級的線程會優先執行,其實這句話的理解應該是,在多線程競爭資源時具有較高優先級的線程會有更大的概率獲取資源,也就是說並不是每次都是它先獲取資源,而是在大量多次的資源競爭中,它獲取到資源的次數會比低優先級的線程多。
說了一些題外話,我們還是要給出正確的交替打印的代碼,供大家參考,其實方式很多,這里只是簡單列出一種:
其實只需要代碼稍微改動就可以解決問題,看代碼:
//A線程類 public class ThreadA extends Thread { private TestThread testThread; public ThreadA(TestThread testThread){ this.testThread=testThread; } @Override public void run(){ int count=0; for(int i=0;i<5;i++){ count++; testThread.printStr(count,"A"); } System.out.println("a線程打印完了"); } } //B線程類 public class ThreadB extends Thread { private TestThread testThread; public ThreadB(TestThread testThread){ this.testThread=testThread; } @Override public void run(){ int count=0; for(int i=0;i<5;i++){ count++; testThread.printStr(count,"B"); } System.out.println("b線程打印完了"); } } //測試類 public class TestThread { public static void main(String[] args){ TestThread testThread=new TestThread(); ThreadA threadA = new ThreadA(testThread); ThreadB threadB = new ThreadB(testThread); threadB.setPriority(1); threadA.setName("threadA"); threadB.setName("threadB"); threadB.start(); threadA.start(); try { threadA.join(); threadB.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("交替打印完成"); } public synchronized void printStr(int count,String str){ String name = Thread.currentThread().getName(); if("A".equals(str)){ System.out.println(name+"-----"+"A"); }else if("B".equals(str)){ System.out.println(name+"-----"+"B"); } try { notify(); if(count!=5){ wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
場景二
因為我們上面的交替輸出的場景過於簡單,我們稍微改變一下,變成對一個字符串'adasdfsafwfvgs'兩個線程交替輸出字符,最后要求A線程執行完任務輸出:“A線程打印完了”,B線程執行完任務輸入:“B線程打印完了”,最后有主線程輸出一句話“我打印完了”!
@Data public class TestThread { volatile static boolean open=false; volatile static int index=0; static String s="adasdfsafwfvgs"; private ThreadA threadA; private ThreadB threadB; public TestThread(){ this.threadA=new ThreadA(); this.threadB=new ThreadB(); } public class ThreadB extends Thread { @Override public void run(){ while(index<s.length()){ if(open){ System.out.println(Thread.currentThread().getName()+"-----"+s.charAt(index++)); open=false; } } System.out.println("b線程打印完了"); } } public class ThreadA extends Thread { @Override public void run(){ while(index< s.length()){ if(!open){ System.out.println(Thread.currentThread().getName()+"-----"+s.charAt(index++)); open=true; } } System.out.println("a線程打印完了"); } } }
測試類:
@RunWith(SpringRunner.class) @SpringBootTest(classes = KafkaClientApplication.class) public class TestThreadTest { /** * 測試使用兩個線程對字符串交替輸出字符的代碼 */ @Test public void test(){ TestThread testThread=new TestThread(); TestThread.ThreadA threadA = testThread.getThreadA(); TestThread.ThreadB threadB = testThread.getThreadB(); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); try { threadA.join(); threadB.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我打印完了"); } }
測試結果: