轉自:詳解使用synchronized解決三個線程依次輪流打印出75個數
原帖見:http://www.iteye.com/topic/1117703
問題描述:
一個關於線程的經典面試題,要求用三個線程,按順序打印1,2,3,4,5.... 71,72,73,74, 75.
線程1先打印1,2,3,4,5, 然後是線程2打印6,7,8,9,10, 然後是線程3打印11,12,13,14,15. 接着再由線程1打印16,17,18,19,20....以此類推, 直到線程3打印到75。
直接上代碼:
- package concurrent.test;
- /**
- * 要求創建三個線程,輸出1-75,
- * 最開始第一個線程輸出1-5,第二個輸出6-10,第三個輸出11-15
- * 接着再第一個線程輸出16-20...就這樣循環下去,直到打印出75個數
- * @author qiaoxueshi
- *
- */
- public class Print1to75 {
- static class Printer implements Runnable{
- static int num = 1; //開始數字
- static final int END = 75;
- int id;
- public Printer(int id) {
- this.id = id;
- }
- @Override
- public void run(){
- synchronized (Printer.class) {
- while(num <= END){
- if(num / 5 % 3 == id){ //如果是屬於自己的數,依次打印出來五個
- System.out.print(id + ":");
- for(int i = 0; i < 5; i++){
- System.out.print(num++ + ", ");
- }
- System.out.println();
- Printer.class.notifyAll();//放棄CPU使用權,喚醒等待在Print.class隊列上的的打印線程
- }else{
- try {
- Printer.class.wait();//如果不屬於自己的數,把當前線程掛在Printer.class這個對象的等待隊列上(也是放棄CPU使用權),等待喚醒
- } catch (InterruptedException e) {
- System.out.println("id" + "被打斷了");
- }
- }
- }
- }
- }
- }
- public static void main(String[] args) {
- //下面可以不按0,1,2的順序來,而且在兩兩中間隨便sleep(),都會正確打印出來
- new Thread( new Printer(0)).start();
- new Thread( new Printer(1)).start();
- new Thread( new Printer(2)).start();
- }
- }
註釋中說的也很明白,有問題歡迎大家討論。
結果(運行了N次,結果都是一致的,請大家檢驗):
- 0:1, 2, 3, 4, 5,
- 1:6, 7, 8, 9, 10,
- 2:11, 12, 13, 14, 15,
- 0:16, 17, 18, 19, 20,
- 1:21, 22, 23, 24, 25,
- 2:26, 27, 28, 29, 30,
- 0:31, 32, 33, 34, 35,
- 1:36, 37, 38, 39, 40,
- 2:41, 42, 43, 44, 45,
- 0:46, 47, 48, 49, 50,
- 1:51, 52, 53, 54, 55,
- 2:56, 57, 58, 59, 60,
- 0:61, 62, 63, 64, 65,
- 1:66, 67, 68, 69, 70,
- 2:71, 72, 73, 74, 75,
注意第23行的synchronized (Printer.class) ,爲什麼是Printer.class,而不是this呢?
是因爲Print.class也是一個對象,在當前JVM中是唯一的,它相當於一個“公證人”,三個線程競爭資源的時候都是從唯一的這個“公證人”手裏拿到許可,才能進入synchronized體。
而如果是synchronized (this)的話,this也相當於一個“公證人”,那麼三個線程各自有一個“公證人”,相當於各幹各的,三個中間沒有競爭關係,構不成同步。
可見只要是這三個的“公證人”是同一個傢伙,就能保持同步,稍微修改一下代碼,我們給三個線程傳進去同一個“公證人”(其實就是一個普通的不能再普通的對象):
- package concurrent.test;
- /**
- * 要求創建三個線程,輸出1-75,
- * 最開始第一個線程輸出1-5,第二個輸出6-10,第三個輸出11-15
- * 接着再第一個線程輸出16-20...就這樣循環下去,直到打印出75個數
- * @author qiaoxueshi
- *
- */
- public class Print1to75 {
- static class Printer implements Runnable{
- static int num = 1; //開始數字
- static final int END = 75;
- int id;
- Object o; //這就是三個線程的“公證人”,有點寒酸吧
- public Printer(int id, Object o) {
- this.id = id;
- this.o = o;
- }
- @Override
- public void run(){
- synchronized (o) {
- while(num <= END){
- if(num / 5 % 3 == id){ //如果是屬於自己的數,依次打印出來五個
- System.out.print(id + ":");
- for(int i = 0; i < 5; i++){
- System.out.print(num++ + ", ");
- }
- System.out.println();
- o.notifyAll();//放棄CPU使用權,喚醒在o對象的等待隊列上的線程
- }else{
- try {
- o.wait(); //如果不屬於自己的數,把當前線程掛在o這個對象的等待隊列上(也放棄了CPU使用權),等待喚醒
- } catch (InterruptedException e) {
- System.out.println("id" + "被打斷了");
- }
- }
- }
- }
- }
- }
- public static void main(String[] args) {
- //下面可以不按0,1,2的順序來,而且在兩兩中間隨便sleep(),都會正確打印出來
- Object o = new Object();
- new Thread( new Printer(0, o)).start();
- new Thread( new Printer(1, o)).start();
- new Thread( new Printer(2, o)).start();
- }
- }
在第16行,添加了三個線程的”公證人“ Object o;
第25,34,37行都由原來的Printer.class改爲了o;
在第50行,創建了一個Object對象,傳給了三個線程。
運行結果和上面的是一模一樣地!
- new Thread( new Printer(0, o)).start();
- new Thread( new Printer(1, o)).start();
- new Thread( new Printer(2, o)).start();
如果覺得這段不太優雅,可以使用ExecuorService來實現,道理是一樣的。