1. 程式人生 > >三、線程同步

三、線程同步

第一個 runnable 執行 兩個 rgs 分析 功能 thread his

利用多線程模擬 3 個窗口賣票

回到頂部

第一種方法:繼承 Thread 類

 創建窗口類 TicketSell 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.ys.thread; public class TicketSell extends Thread{ //定義一共有 50 張票,註意聲明為 static,表示幾個窗口共享 private static int num = 50; //調用父類構造方法,給線程命名
public TicketSell(String string) { super(string); } @Override public void run() { //票分 50 次賣完 for(int i = 0 ; i < 50 ;i ++){ if(num > 0){ try { sleep(10);//模擬賣票需要一定的時間 } catch (InterruptedException e) {
// 由於父類的 run()方法沒有拋出任何異常,根據繼承的原則,子類拋出的異常不能大於父類, 故我們這裏也不能拋出異常 e.printStackTrace(); } System.out.println(this.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張"); } } } }

  創建主線程測試:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.ys.thread; public class TestTicket { public static void main(String[] args) { //創建 3 個窗口 TicketSell t1 = new TicketSell("A窗口"); TicketSell t2 = new TicketSell("B窗口"); TicketSell t3 = new TicketSell("C窗口"); //啟動 3 個窗口進行買票 t1.start(); t2.start(); t3.start(); } }

  結果:這裏我們省略了一些,根據電腦配置,結果會隨機出現不同

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 B窗口賣出一張票,剩余48 A窗口賣出一張票,剩余47 C窗口賣出一張票,剩余49 C窗口賣出一張票,剩余46 B窗口賣出一張票,剩余44 A窗口賣出一張票,剩余45 A窗口賣出一張票,剩余43 ... C窗口賣出一張票,剩余5 A窗口賣出一張票,剩余4 B窗口賣出一張票,剩余3 A窗口賣出一張票,剩余2 C窗口賣出一張票,剩余3 B窗口賣出一張票,剩余1 C窗口賣出一張票,剩余0 A窗口賣出一張票,剩余-1
回到頂部

第二種方法:實現 Runnable 接口

  創建窗口類 TicketSellRunnable

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.ys.thread; public class TicketSellRunnable implements Runnable{ //定義一共有 50 張票,繼承機制開啟線程,資源是共享的,所以不用加 static private int num = 50; @Override public void run() { //票分 50 次賣完 for(int i = 0 ; i < 50 ;i ++){ if(num > 0){ try { //模擬賣一次票所需時間 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張"); } } } }

  創建主線程測試:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.ys.thread; public class TicketSellRunnableTest { public static void main(String[] args) { TicketSellRunnable t = new TicketSellRunnable(); Thread t1 = new Thread(t,"A窗口"); Thread t2 = new Thread(t,"B窗口"); Thread t3 = new Thread(t,"C窗口"); t1.start(); t2.start(); t3.start(); } }

  結果:同理為了篇幅我們也省略了中間的一些結果

1 2 3 4 5 6 7 8 9 10 11 12 13 14 B窗口賣出一張票,剩余49 C窗口賣出一張票,剩余48 A窗口賣出一張票,剩余49 B窗口賣出一張票,剩余47 A窗口賣出一張票,剩余45 ...... A窗口賣出一張票,剩余4 C窗口賣出一張票,剩余5 A窗口賣出一張票,剩余3 B窗口賣出一張票,剩余2 C窗口賣出一張票,剩余1 B窗口賣出一張票,剩余0 A窗口賣出一張票,剩余-2 C窗口賣出一張票,剩余-1

  

結果分析:這裏出現了票數為 負數的情況,這在現實生活中肯定是不存在的,那麽為什麽會出現這樣的情況呢?

  技術分享圖片

解決辦法分析:即我們不能同時讓超過兩個以上的線程進入到 if(num>0)的代碼塊中,不然就會出現上述的錯誤。我們可以通過以下三個辦法來解決:

1、使用 同步代碼塊

2、使用 同步方法

3、使用 鎖機制

①、使用同步代碼塊

1 2 3 4 5 6 7 8 9 語法: synchronized (同步鎖) { //需要同步操作的代碼 } 同步鎖:為了保證每個線程都能正常的執行原子操作,Java 線程引進了同步機制;同步鎖也叫同步監聽對象、同步監聽器、互斥鎖; Java程序運行使用的任何對象都可以作為同步監聽對象,但是一般我們把當前並發訪問的共同資源作為同步監聽對象 註意:同步鎖一定要保證是確定的,不能相對於線程是變化的對象;任何時候,最多允許一個線程拿到同步鎖,誰拿到鎖誰進入代碼塊,而其他的線程只能在外面等著

  實例:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public void run() { //票分 50 次賣完 for(int i = 0 ; i < 50 ;i ++){ //這裏我們使用當前對象的字節碼對象作為同步鎖 synchronized (this.getClass()) { if(num > 0){ try { //模擬賣一次票所需時間 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張"); } } } }

②、使用 同步方法

語法:即用 synchronized 關鍵字修飾方法

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override public void run() { //票分 50 次賣完 for(int i = 0 ; i < 50 ;i ++){ sell(); } } private synchronized void sell(){ if(num > 0){ try { //模擬賣一次票所需時間 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張"); } }

  註意:不能直接用 synchronized 來修飾 run() 方法,因為如果這樣做,那麽就會總是第一個線程進入其中,而這個線程執行完所有操作,即賣完所有票了才會出來。

③、使用 鎖機制

1 public interface Lock

  主要方法:

技術分享圖片

  常用實現類:

1 2 3 public class ReentrantLock extends Object implements Lock, Serializable<br>//一個可重入互斥Lock具有與使用synchronized方法和語句訪問的隱式監視鎖相同的基本行為和語義,但具有擴展功能。

  例子:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.ys.thread; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TicketSellRunnable implements Runnable{ //定義一共有 50 張票,繼承機制開啟線程,資源是共享的,所以不用加 static private int num = 50; //創建一個鎖對象 Lock l = new ReentrantLock(); @Override public void run() { //票分 50 次賣完 for(int i = 0 ; i < 50 ;i ++){ //獲取鎖 l.lock(); try { if(num > 0){ //模擬賣一次票所需時間 Thread.sleep(10); System.out.println(Thread.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張"); } } catch (Exception e) { e.printStackTrace(); }finally{ //釋放鎖 l.unlock(); } } } private void sell(){ } }

三、線程同步