Java多線程售票程序分析

一、售票程序V1安全

public class TicketSoldV1 { public static void main(String[] args) { TicketWindowV1 r1 = new TicketWindowV1(); TicketWindowV1 r2 = new TicketWindowV1(); TicketWindowV1 r3 = new TicketWindowV1(); new Thread(r1, "A窗口").start(); //啓動三個線程窗口
        new Thread(r2, "B窗口").start(); new Thread(r3, "C窗口").start(); } } class TicketWindowV1 implements Runnable { private static int ticketNumber = 100; //靜態變量,全部售票窗口共享該
    
    public void run() { System.out.println(Thread.currentThread().getName() + "線程開始運行..."); while(true) { //循環賣票
            if(ticketNumber > 0) { //打印所賣票的票號
                System.out.println(Thread.currentThread().getName() + ":" + ticketNumber); ticketNumber--; } else { break; } } System.out.println(Thread.currentThread().getName() + "線程結束運行..."); } }
View Code

上述程序運行結果以下所示:多線程

A窗口線程開始運行... C窗口線程開始運行... B窗口線程開始運行... B窗口:100 C窗口:100 A窗口:100 A窗口:97 A窗口:96 C窗口:98 B窗口:99 C窗口:94 A窗口:95 C窗口:92 B窗口:93 B窗口:89 C窗口:90 A窗口:91 C窗口:87 B窗口:88 C窗口:85 A窗口:86 C窗口:83 B窗口:84 B窗口:80 C窗口:81 A窗口:82 A窗口:77 C窗口:78 B窗口:79 B窗口:74 B窗口:73 C窗口:75 A窗口:76 C窗口:71 B窗口:72 B窗口:68 C窗口:69 A窗口:70 C窗口:66 B窗口:67 C窗口:64 A窗口:65 A窗口:61 A窗口:60 C窗口:62 B窗口:63 B窗口:57 C窗口:58 A窗口:59 C窗口:55 B窗口:56 C窗口:53 A窗口:54 A窗口:50 C窗口:51 B窗口:52 C窗口:48 A窗口:49 C窗口:46 B窗口:47 C窗口:44 A窗口:45 C窗口:42 B窗口:43 C窗口:40 A窗口:41 C窗口:38 B窗口:39 C窗口:36 A窗口:37 C窗口:34 B窗口:35 C窗口:32 A窗口:33 C窗口:30 B窗口:31 C窗口:28 A窗口:29 C窗口:26 B窗口:27 C窗口:24 A窗口:25 C窗口:22 B窗口:23 C窗口:20 A窗口:21 C窗口:18 B窗口:19 C窗口:16 A窗口:17 C窗口:14 B窗口:15 C窗口:12 A窗口:13 C窗口:10 B窗口:11 C窗口:8 A窗口:9 A窗口:5 C窗口:6 B窗口:7 C窗口:3 A窗口:4 C窗口:1 B窗口:2 C窗口線程結束運行... A窗口線程結束運行... B窗口線程結束運行...
View Code

從上述結果中能夠看到三個售票窗口線程交替賣票,符合咱們的需求,可是從中也看到了該程序存在線程安全問題。ide

 

二、線程安全spa

問題1:線程

A、B、C窗口重複賣出了100號票;3d

問題2:code

有可能出現賣出0號票的狀況(票號:1-100);blog

問題3:繼承

有可能出現某些票號沒有賣出的狀況;資源

咱們能夠人爲驗證線程安全問題,在Java中在Thread類中提供了sleep方法,可使線程交出cpu執行權,「睡一下」,模擬真實的賣票場景。

售票程序V2(驗證線程不安全):

//線程不安全驗證
public class TicketSoldV2 { public static void main(String[] args) { TicketWindowV2 r1 = new TicketWindowV2(); TicketWindowV2 r2 = new TicketWindowV2(); TicketWindowV2 r3 = new TicketWindowV2(); new Thread(r1, "A窗口").start(); //啓動三個線程窗口
        new Thread(r2, "B窗口").start(); new Thread(r3, "C窗口").start(); } } class TicketWindowV2 implements Runnable { private static int ticketNumber = 100; //靜態變量,全部售票窗口共享該
    
    public void run() { System.out.println(Thread.currentThread().getName() + "線程開始運行..."); while(true) { //循環賣票
            if(ticketNumber > 0) { //打印所賣票的票號
                System.out.println(Thread.currentThread().getName() + ":" + ticketNumber); ticketNumber--; try { //sleep方法會拋出異常
                    Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } System.out.println(Thread.currentThread().getName() + "線程結束運行..."); } }
View Code

上述程序運行結果以下所示:

A窗口線程開始運行... A窗口:100 C窗口線程開始運行... C窗口:99 B窗口線程開始運行... B窗口:98 B窗口:97 C窗口:96 A窗口:95 C窗口:94 A窗口:93 B窗口:92 B窗口:91 C窗口:90 A窗口:90 B窗口:88 A窗口:87 C窗口:86 B窗口:85 C窗口:85 A窗口:85 B窗口:82 C窗口:81 A窗口:80 C窗口:79 A窗口:78 B窗口:77 B窗口:76 C窗口:75 A窗口:74 B窗口:73 C窗口:72 A窗口:71 B窗口:70 C窗口:69 A窗口:68 A窗口:67 C窗口:66 B窗口:65 B窗口:64 C窗口:64 A窗口:64 B窗口:61 A窗口:61 C窗口:61 B窗口:58 A窗口:58 C窗口:56 B窗口:55 A窗口:54 C窗口:53 B窗口:52 A窗口:51 C窗口:50 B窗口:49 A窗口:48 C窗口:47 A窗口:46 C窗口:45 B窗口:44 B窗口:43 A窗口:43 C窗口:41 A窗口:40 C窗口:40 B窗口:40 A窗口:37 B窗口:36 C窗口:36 A窗口:34 C窗口:34 B窗口:34 B窗口:31 C窗口:30 A窗口:30 C窗口:28 A窗口:27 B窗口:26 C窗口:25 B窗口:25 A窗口:24 B窗口:22 C窗口:21 A窗口:20 B窗口:19 A窗口:19 C窗口:19 B窗口:16 A窗口:16 C窗口:14 A窗口:13 B窗口:13 C窗口:12 B窗口:10 A窗口:9 C窗口:9 C窗口:7 A窗口:6 B窗口:5 C窗口:4 A窗口:3 B窗口:2 B窗口:1 C窗口:0 A窗口線程結束運行... B窗口線程結束運行... C窗口線程結束運行...
View Code

能夠看到有些票被賣了屢次(90,85,64等),有些票沒有被賣出(89,63,39等),且出現了0號票。

 

三、線程安全的解決:同步

能夠看到線程安全問題是因爲多個線程同時操做一個共享的資源形成的,那麼咱們若是在一個線程使用這個資源時,禁止其餘線程再獲取這個資源,也就是一次只能有一個線程操做這個資源,這種狀態成爲同步。Java專門爲多線程安全問題提供瞭解決機制:同步代碼塊,將操做共享資源的代碼放到同步代碼塊中。

synchronized(鎖) { 須要被同步的代碼 }
View Code

 

四、售票程序V3

找到全部"讀取、修改"共享資源的語句,將他們放在同步代碼塊中。

public class TicketSoldV3 { public static void main(String[] args) { TicketWindowV3 r1 = new TicketWindowV3(); TicketWindowV3 r2 = new TicketWindowV3(); TicketWindowV3 r3 = new TicketWindowV3(); new Thread(r1, "A窗口").start(); //啓動三個線程窗口
        new Thread(r2, "B窗口").start(); new Thread(r3, "C窗口").start(); } } class TicketWindowV3 implements Runnable { private static int ticketNumber = 100; //靜態變量,全部售票窗口共享該
    
    public void run() { System.out.println(Thread.currentThread().getName() + "線程開始運行..."); while(true) { //循環賣票
            synchronized (TicketWindowV3.class) { if(ticketNumber > 0) { //打印所賣票的票號
                    System.out.println(Thread.currentThread().getName() + ":" + ticketNumber); ticketNumber--; try { //sleep方法會拋出異常
                        Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } System.out.println(Thread.currentThread().getName() + "線程結束運行..."); } }
View Code

上述程序運行結果正常,以下所示:

A窗口線程開始運行... A窗口:100 C窗口線程開始運行... B窗口線程開始運行... A窗口:99 B窗口:98 B窗口:97 B窗口:96 B窗口:95 C窗口:94 B窗口:93 A窗口:92 B窗口:91 B窗口:90 B窗口:89 B窗口:88 B窗口:87 B窗口:86 B窗口:85 B窗口:84 B窗口:83 B窗口:82 C窗口:81 C窗口:80 C窗口:79 B窗口:78 B窗口:77 B窗口:76 B窗口:75 B窗口:74 B窗口:73 B窗口:72 B窗口:71 A窗口:70 B窗口:69 B窗口:68 B窗口:67 B窗口:66 B窗口:65 B窗口:64 B窗口:63 B窗口:62 B窗口:61 C窗口:60 B窗口:59 B窗口:58 B窗口:57 B窗口:56 B窗口:55 B窗口:54 B窗口:53 B窗口:52 B窗口:51 B窗口:50 B窗口:49 B窗口:48 A窗口:47 B窗口:46 B窗口:45 B窗口:44 B窗口:43 B窗口:42 B窗口:41 B窗口:40 B窗口:39 B窗口:38 B窗口:37 B窗口:36 C窗口:35 B窗口:34 A窗口:33 B窗口:32 C窗口:31 B窗口:30 A窗口:29 A窗口:28 B窗口:27 B窗口:26 B窗口:25 B窗口:24 B窗口:23 B窗口:22 C窗口:21 B窗口:20 B窗口:19 B窗口:18 B窗口:17 B窗口:16 A窗口:15 A窗口:14 B窗口:13 B窗口:12 B窗口:11 B窗口:10 B窗口:9 C窗口:8 B窗口:7 B窗口:6 B窗口:5 B窗口:4 B窗口:3 B窗口:2 B窗口:1 B窗口線程結束運行... A窗口線程結束運行... C窗口線程結束運行...
View Code

 擴展問題:若是把整個run方法中的代碼都放到同步代碼塊裏會出現什麼狀況呢?

public class TicketSoldV4 { public static void main(String[] args) { TicketWindowV4 r1 = new TicketWindowV4(); TicketWindowV4 r2 = new TicketWindowV4(); TicketWindowV4 r3 = new TicketWindowV4(); new Thread(r1, "A窗口").start(); //啓動三個線程窗口
        new Thread(r2, "B窗口").start(); new Thread(r3, "C窗口").start(); } } class TicketWindowV4 implements Runnable { private static int ticketNumber = 100; //靜態變量,全部售票窗口共享該
    
    public void run() { System.out.println(Thread.currentThread().getName() + "線程開始運行..."); synchronized (TicketSoldV4.class) { while(true) { //循環賣票
                if(ticketNumber > 0) { //打印所賣票的票號
                    System.out.println(Thread.currentThread().getName() + ":" + ticketNumber); ticketNumber--; try { //sleep方法會拋出異常
                        Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } System.out.println(Thread.currentThread().getName() + "線程結束運行..."); } }
View Code

上述程序運行結果正常,以下所示:

A窗口線程開始運行... A窗口:100 C窗口線程開始運行... B窗口線程開始運行... A窗口:99 A窗口:98 A窗口:97 A窗口:96 A窗口:95 A窗口:94 A窗口:93 A窗口:92 A窗口:91 A窗口:90 A窗口:89 A窗口:88 A窗口:87 A窗口:86 A窗口:85 A窗口:84 A窗口:83 A窗口:82 A窗口:81 A窗口:80 A窗口:79 A窗口:78 A窗口:77 A窗口:76 A窗口:75 A窗口:74 A窗口:73 A窗口:72 A窗口:71 A窗口:70 A窗口:69 A窗口:68 A窗口:67 A窗口:66 A窗口:65 A窗口:64 A窗口:63 A窗口:62 A窗口:61 A窗口:60 A窗口:59 A窗口:58 A窗口:57 A窗口:56 A窗口:55 A窗口:54 A窗口:53 A窗口:52 A窗口:51 A窗口:50 A窗口:49 A窗口:48 A窗口:47 A窗口:46 A窗口:45 A窗口:44 A窗口:43 A窗口:42 A窗口:41 A窗口:40 A窗口:39 A窗口:38 A窗口:37 A窗口:36 A窗口:35 A窗口:34 A窗口:33 A窗口:32 A窗口:31 A窗口:30 A窗口:29 A窗口:28 A窗口:27 A窗口:26 A窗口:25 A窗口:24 A窗口:23 A窗口:22 A窗口:21 A窗口:20 A窗口:19 A窗口:18 A窗口:17 A窗口:16 A窗口:15 A窗口:14 A窗口:13 A窗口:12 A窗口:11 A窗口:10 A窗口:9 A窗口:8 A窗口:7 A窗口:6 A窗口:5 A窗口:4 A窗口:3 A窗口:2 A窗口:1 C窗口線程結束運行... A窗口線程結束運行... B窗口線程結束運行...
View Code

執行能夠發現全部的票都是從A窗口賣出的:這是由於窗口A繼承進入同步代碼塊,鎖上鎖,執行while循環,循環過程當中cpu切換到其餘線程,例如窗口B線程執行run,發現鎖處於鎖定狀態,不執行操做,cpu再切換,最後cpu只能繼續執行窗口A線程,實際上這就變成了單線程了。

從這裏能夠發現:不該該將過多的代碼放到同步代碼塊中。只將全部"讀取、修改"共享資源的語句放在同步代碼塊中。

相關文章
相關標籤/搜索