兩個線程交替打印/賣票問題?(5種方法)

引言

     設置兩個窗口(線程),交替賣出100張票,一個線程賣寄數,一個線程賣偶數,要求交替賣出,最後數據 1 ,2,,3,4,5,6......100

 1 /**
 2  * @ClassName AlternatePrintThread
 3  * @Description 設計兩個線程,賣出1-100張票,一個線程打印奇數張,另一個線程打印偶數張,要求交替打印。 最後輸出123……100
 4  * @Author zhao's'qing
 5  * @Date 2022/11/10  15:21
 6  * @Version 1.0
 7  **/
 8 public class AlternatePrintThread {
 9 
10     static final Object obj = new Object();
11     private static int flag = 0;
12 
13     private static volatile boolean flag2 = false;
14 
15     private static Lock lock = new ReentrantLock();
16     private static Condition condition = lock.newCondition();
17     private static boolean flag3 = false;
18 
19     public static void main(String[] args) {
20         AlternatePrintThread apt = new AlternatePrintThread();
21         apt.test1();
22         //apt.test2();
23         //apt.test3();
24         //apt.test4();
25         // apt.test5();
26 
27     }
28 }

方法一(Synchronized+共享變量)
    解題思路
    新建一個對象,並定義一個共享變量。只要flag不爲1,線程t2就會阻塞,釋放鎖資源,所以t1線程先執行,此時flag爲0,跳過while判斷,然後修改flag爲1,打印奇數張票,並喚醒t2,由於flag被改爲1,下次循環,t1就會阻塞。t2被喚醒後,t1已經釋放了鎖資源,所以t2可以獲取鎖資源,並且flag爲1,跳過while循環,修改flag爲0,打印偶數張票,喚醒t1。

    代碼實現:

 1 public void test1(){
 2         Thread t1 = new Thread(() -> {
 3             synchronized (obj) {
 4                 for (int i = 1; i <= 10; i += 2) {
 5                     while (flag != 0) {
 6                         try {
 7                             obj.wait();
 8                             Thread.sleep(200);
 9                         } catch (Exception e) {
10                             e.printStackTrace();
11                         }
12                     }
13                     flag = 1;
14                     System.out.println(Thread.currentThread().getName() + " 賣出第 :" + i +" 張票!");
15                     obj.notify();
16 
17                 }
18             }
19         }, "線程A");
20 
21         Thread t2 = new Thread(()->{
22             synchronized (obj) {
23                 for (int i = 2; i <= 10; i += 2) {
24                     while (flag != 1) {
25                         try {
26                             obj.wait();
27                             Thread.sleep(200);
28                         } catch (Exception e) {
29                             e.printStackTrace();
30                         }
31                     }
32                     flag = 0;
33                     System.out.println(Thread.currentThread().getName() + " 賣出第:" + i +" 張票");
34                     obj.notify();
35                 }
36             }
37         },"線程B");

 結果:

 

方法二(Volatile)
    解題思路
    定義一個volatile修飾的共享變量flag,當flag爲false時,t2線程讓出系統資源,自己進入就緒狀態,讓t1先執行,t1獲取資源後,首先跳過while判斷,打印奇數,修改flag爲true,下次循環時,就會讓出資源,此時t2從就緒狀態進入執行狀態,跳過while判斷,打印偶數,修改flag爲false,下次循環時,就會讓出資源。如此不斷交替執行,直到打印完所有奇數偶數張票。

    代碼實現:

 1  //方法二(Volatile)
 2     public void test2(){
 3         Thread t1 = new Thread(() -> {
 4             for (int i = 1; i <= 100; i += 2) {
 5                 while (flag2) {
 6                     Thread.yield();
 7                 }
 8                 System.out.println(Thread.currentThread().getName() + " 賣出第 :" + i +" 張票!");
 9                 flag2 = true;
10             }
11 
12         }, "線程A");
13 
14         Thread t2 = new Thread(() -> {
15             for (int i = 2; i <= 100; i += 2) {
16                 while (!flag2) {
17                     Thread.yield();
18                 }
19                 System.out.println(Thread.currentThread().getName() + " 賣出第 :" + i +" 張票!");
20                 flag2 = false;
21             }
22         }, "線程B");
23         t1.start();
24         t2.start();
25     }

 結果:

   

方法三(Semaphore)
    解題思路
    定義兩個信號量,一個許可爲1,一個許可爲0。首先許可爲0的會阻塞,所以t1線程先執行,通過s1.acquire()消耗許可,打印奇數,此時s1許可爲0,t1阻塞,同時s2.release()獲得一個許可,t2線程通過s2.acquire()消耗許可,打印偶數,此時s2許可又變爲0,t2阻塞,同時s1.release()獲得一個許可。如此反覆執行,直到打印完所有的數。

    代碼實現:

 1     //方法三(Semaphore)
 2     public void test3(){
 3         Semaphore s1 = new Semaphore(1);
 4         Semaphore s2 = new Semaphore(0);
 5         Thread t1 = new Thread(() -> {
 6             for (int i = 1; i <= 100; i += 2) {
 7                 try {
 8                     s1.acquire(); //獲取令牌
 9                     System.out.println(Thread.currentThread().getName() + " 賣出第 :" + i +" 張票!");
10                     s2.release(); //釋放令牌
11                 } catch (Exception e) {
12                     e.printStackTrace();
13                 }
14 
15             }
16         }, "線程A");
17         Thread t2 = new Thread(() -> {
18             for (int i = 2; i <= 100; i += 2) {
19                 try {
20                     s2.acquire();
21                     System.out.println(Thread.currentThread().getName() + " 賣出第 :" + i +" 張票!");
22                     s1.release();
23                 } catch (Exception e) {
24                     e.printStackTrace();
25                 }
26             }
27         }, "線程B");
28         t1.start();
29         t2.start();
30     }

 結果:

 

方法四(ReentrantLock + Condition)
    解題思路
    首先定義一個lock和一個condition。當flag爲false時,t2會阻塞,此時t1先執行,打印奇數張票,通過condition喚醒t2,修改flag爲true,下次循環自己就會進入阻塞狀態。t2被喚醒後,由於flag已經變爲true,跳過while判斷,打印偶數張票,喚醒t1,修改flag,自己阻塞,如此反覆。

    代碼實現:

 1 //方法四(ReentrantLock)
 2     public void test4(){
 3         Thread t1 = new Thread(() -> {
 4             for (int i = 1; i <= 100; i += 2) {
 5                 lock.lock();
 6                 while (flag3) {
 7                     try {
 8                         condition.await();
 9                         Thread.sleep(200);
10                     } catch (Exception e) {
11                         e.printStackTrace();
12                     }
13                 }
14                 System.out.println(Thread.currentThread().getName() + " 賣出第:" + i +"張票");
15                 condition.signal();
16                 flag3 = true;
17                 lock.unlock();
18             }
19         }, "窗口1");
20 
21         Thread t2 = new Thread(() -> {
22             for (int i = 2; i <= 100; i += 2) {
23                 lock.lock();
24                 while (!flag3) {
25                     try {
26                         condition.await();
27                         Thread.sleep(200);
28                     } catch (Exception e) {
29                        e.printStackTrace();
30                     }
31                 }
32                 System.out.println(Thread.currentThread().getName() + " 賣出第:" + i +"張票");
33                 condition.signal();
34                 flag3 = false;
35                 lock.unlock();
36             }
37         }, "窗口2");
38         t1.start();
39         t2.start();
40     }

 結果:

 

方法五(SynchronousQueue阻塞隊列)
    解題思路

     定義一個阻塞隊列,t2線程調用put或tranfer往隊列裏放數據時,會阻塞,所以t1先執行,取走之前放進來的奇數張票,並打印,然後將偶數put或tranfer到隊列,t1阻塞,t2繼續執行,如此反覆執行。

    代碼實現:

 1 //方法五(SynchronousQueue阻塞隊列)
 2     public void test5(){
 3         //定義奇偶數數組
 4         int[] even = new int[50];
 5         int[] odd =  new int[50];
 6         int a =0;
 7         for(int i=0;i<100;i++){
 8             if(i%2==0){
 9                 odd[a]=i+1; //偶數 2 4 6 8 10
10                 a++;
11             }else{
12                 even[a-1]=i+1; //奇數 1 3 5 7 9
13             }
14         }
15         SynchronousQueue<Integer> queue=new SynchronousQueue<>();
16         new Thread(()->{
17             try{
18                 for(int i:even){
19                     System.out.println(Thread.currentThread().getName() + " 賣出第:" + queue.take() +"張票");
20                     queue.put(i);
21                 }
22             }
23             catch(Exception e){
24                 e.printStackTrace();
25             }
26         },"線程A").start();
27         new Thread(()->{
28             try{
29                 for(int i:odd){
30                     queue.put(i);
31                     System.out.println(Thread.currentThread().getName() + " 賣出第:" + queue.take()  +"張票");
32                 }
33             }
34             catch(Exception e){
35                 e.printStackTrace();
36             }
37         },"線程B").start();
38     }
SynchronousQueue是一個沒有容量的隊列,它的put操作和take操作之間是相互依賴的,即put操作必須在take操作準備好時才能將元素“推”過去,反之take操作也必須在put操作準備推元素的時候才能獲取到元素。有人可能會說只有1個容量大小的BlockingQueue也能實現該操作,但是它們之間有着本質的不同
1、SynchronousQueue在put時,如果另一個線程沒有執行take操作,put線程會一直阻塞;而BlockingQueue在put一個元素時,第一次是不會阻塞的,只有第二次因爲容量滿了put操作才阻塞,而SynchronousQueue在第一次就阻塞;
2、從第1點就可以看出SynchronousQueue容量爲0,而BlockingQueue至少有容納1個元素的空間。

 結果:

 

 

 至此:5中交替打印賣票的方法就總結完了!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章