一、面試碰到過這樣的問題:
讓兩個多線程依次打印奇數和偶數。
二、問題解釋:
大家都知道,線程一旦啟動,CPU有一定的調度策略,不是人為能隨便控制的。這樣的題目在實際工程中也沒有多大的意義,不用多線程,直接用方法依次調用就可以了,但為什么還要考這樣的題目,實際上是考對多線程和線程之間的通信相關知識點。現在總結如下:
三、線程控制和線程之間的通信
主要是三個方法wait(); notify(); notifyAll();
1、wait()、notify/notifyAll() 方法是Object的本地final方法,無法被重寫。
2、wait()使當前線程阻塞,前提是 必須先獲得鎖,一般配合synchronized 關鍵字使用,即,一般在synchronized 同步代碼塊里使用 wait()、notify/notifyAll() 方法。
3、 由於 wait()、notify/notifyAll() 在synchronized 代碼塊執行,說明當前線程一定是獲取了鎖的。
當線程執行wait()方法時候,會釋放當前的鎖,然后讓出CPU,進入等待狀態。
只有當 notify/notifyAll() 被執行時候,才會喚醒一個或多個正處於等待狀態的線程,然后繼續往下執行,直到執行完synchronized 代碼塊的代碼或是中途遇到wait() ,再次釋放鎖。
也就是說,notify/notifyAll() 的執行只是喚醒沉睡的線程,而不會立即釋放鎖,鎖的釋放要看代碼塊的具體執行情況。所以在編程中,盡量在使用了notify/notifyAll() 后立即退出臨界區,以喚醒其他線程
4、wait() 需要被try catch包圍,中斷也可以使wait等待的線程喚醒。
5、notify 和wait 的順序不能錯,如果A線程先執行notify方法,B線程在執行wait方法,那么B線程是無法被喚醒的。
6、notify 和 notifyAll的區別
notify方法只喚醒一個等待(對象的)線程並使該線程開始執行。所以如果有多個線程等待一個對象,這個方法只會喚醒其中一個線程,選擇哪個線程取決於操作系統對多線程管理的實現。notifyAll 會喚醒所有等待(對象的)線程,盡管哪一個線程將會第一個處理取決於操作系統的實現。如果當前情況下有多個線程需要被喚醒,推薦使用notifyAll 方法。比如在生產者-消費者里面的使用,每次都需要喚醒所有的消費者或是生產者,以判斷程序是否可以繼續往下執行。
7、wait(), notify(), notifyAll() 必須是同一對象的多個線程之間的通信
四、面試題舉例
兩個線程,依次執行打印奇數和偶數。
因為wait() notifyAll() 必須是同一對象的多個線程之間的通信,所以有如下程序:
1 public class Printer { 2 3 int i = 1; 4 5 synchronized public void printEven(){ 6 for(;i<=100;) { 7 if (i % 2 == 1) { 8 try { 9 wait(); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 } else { 14 System.out.println(Thread.currentThread().getName() + " is printting i: " + i++); 15 notifyAll(); 16 } 17 } 18 } 19 20 synchronized public void printOdd(){ 21 for(; i<=100; ) { 22 if (i % 2 == 0) { 23 try { 24 wait(); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } else { 29 System.out.println(Thread.currentThread().getName() + " is printting i: " + i++); 30 notifyAll(); 31 } 32 } 33 } 34 35 }
要實現兩個線程分別打印,所以有如下代碼:
1 public class PrintEven implements Runnable{ 2 Printer printer; 3 public PrintEven(Printer printer){ 4 this.printer = printer; 5 } 6 public void run() { 7 printer.printEven(); 8 9 } 10 }
1 public class PrintOdd implements Runnable { 2 Printer printer; 3 public PrintOdd(Printer printer){ 4 this.printer = printer; 5 } 6 public void run() { 7 printer.printOdd(); 8 9 } 10 }
主函數: 把printer作為對象傳入
1 public static void main(String[] args){ 2 3 Printer printer = new Printer(); 4 5 Runnable printOdd = new PrintOdd(printer); 6 Thread threadOdd = new Thread(printOdd, "thread1"); 7 8 Runnable printEven = new PrintEven(printer); 9 Thread threadEven = new Thread(printEven, "thread2"); 10 11 threadOdd.start(); 12 threadEven.start(); 13 }
程序運行結果:
1 thread1 is printting i: 1 2 thread2 is printting i: 2 3 thread1 is printting i: 3 4 thread2 is printting i: 4 5 thread1 is printting i: 5 6 thread2 is printting i: 6 7 thread1 is printting i: 7 8 thread2 is printting i: 8 9 thread1 is printting i: 9 10 thread2 is printting i: 10 11 thread1 is printting i: 11 12 thread2 is printting i: 12 13 thread1 is printting i: 13 14 thread2 is printting i: 14 15 thread1 is printting i: 15 16 thread2 is printting i: 16 17 thread1 is printting i: 17 18 thread2 is printting i: 18 19 thread1 is printting i: 19 20 thread2 is printting i: 20