引言
設置兩個窗口(線程),交替賣出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中交替打印賣票的方法就總結完了!