JAVA學習之路(多執行緒)—模擬售票(細解)

HoweZhan發表於2018-12-27

首先看題目描述:

假設有火車票100張,建立4個執行緒模擬4個售票點,每100ms售出一張,列印出售票過程,格式如下:

視窗3:賣出第100張票

視窗4:賣出第99張票 

…………

…………

簡單的思路就是建立一個類,首先肯定要去繼承Thread。開啟執行緒,由於是4個視窗,肯定要開啟4個執行緒。然後讓每個執行緒去輸出結果,也就是賣出去的票。這裡很多人想不到如何讓4個執行緒不列印重複的票。(比如4個執行緒都賣出去了第100張票,這顯然是不合理的)。

看程式碼:

  

 1 package com.lesson.thread;
 2 
 3 public class MyThread {
 4 
 5     public static void main(String[] args) {
 6         Ticket sell1 = new Ticket();
 7         Ticket sell2 = new Ticket();
 8         Ticket sell3 = new Ticket();
 9         Ticket sell4 = new Ticket();
10         sell1.setName("視窗1");
11         sell2.setName("視窗2");
12         sell3.setName("視窗3");
13         sell4.setName("視窗4");
14         sell1.start();
15         sell2.start();
16         sell3.start();
17         sell4.start();
18     }
19 }
20 class Ticket extends Thread {
21     private static int tickets = 100;//這裡設定成static,目的是讓每個執行緒共享這個變數。以免出現重複列印的現象。
22     @Override
23     public void run() {
24         while(true) {
25             if(tickets <= 0) {
26                 break;
27             }
2829          System.out.println(getName()+":買出第"+tickets--+"張票。");//賣出一張減一張票
30 31         }
32     }
33     
34 }

可能你已經看到了你想要的結果了。但是,還沒完。目前程式碼寫到這裡是有問題的!!!

為什麼?

看下面的程式碼:

package thread;
public class Mythread {

    public static void main(String[] args) {
        Ticket sell1 = new Ticket();
        Ticket sell2 = new Ticket();
        Ticket sell3 = new Ticket();
        Ticket sell4 = new Ticket();
        sell1.setName("視窗1");
        sell2.setName("視窗2");
        sell3.setName("視窗3");
        sell4.setName("視窗4");
        sell1.start();
        sell2.start();
        sell3.start();
        sell4.start();
    }
}
class Ticket extends Thread {
    private static int tickets = 100;
    @Override
    public void run() {
        while(true) {
            if(tickets <= 0) {
                break;
            }
            try {
                Thread.sleep(10);          //讓進來的執行緒睡10ms;執行緒1,2,3,4都睡在這
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+":買出了第"+tickets--+"張票。");
        }
    }    
}

與開始的程式碼不同在於讓進來的執行緒睡一會。可以看到如下執行結果:

視窗2:買出了第0張票。
視窗3:買出了第-1張票。
視窗1:買出了第-2張票

這裡編者調出來了出問題的地方。可以看到怎麼會第0,-1,-2張票???

做出解釋:

其實在這裡讓執行緒睡一會就是為了演示這裡有很多行程式碼要執行。假設票已經賣到第1張了,也就是tickets=1,然後第一條執行緒進來判斷 tickets <= 0 ?不成立,然後執行緒1要睡 10ms,緊接著,執行緒2進來,這時 tickets 還是為1,因為執行緒1在睡,tickets 沒有減 。然後執行緒2判斷 tickets <= 0 ? 還是不成立,執行緒2又開始睡。同樣,執行緒3,執行緒4都睡了。 這時的tickets 還是等於1的。然後執行緒1先醒過來,開始輸出結果,tickets 減了1。可是這是其他的執行緒還是經過while裡面的判斷語句進來了的,只是睡了。所以當其他執行緒醒過來的時候,還是會列印出結果的。也就出現了上面的問題。

解決方法:

 多執行緒併發改變同一變數,為了解決,採用同步程式碼塊synchronized。裡面加任意的物件,但是不能加this,因為這裡建立了四個執行緒,每一個執行緒都有自己的物件,所以是四個不同的物件,沒有用。所以這裡不能用this,必須鎖在同一個物件裡才行。而Thickets.class這是唯一的。

package thread;
public class Mythread {

    public static void main(String[] args) {
        Ticket sell1 = new Ticket();
        Ticket sell2 = new Ticket();
        Ticket sell3 = new Ticket();
        Ticket sell4 = new Ticket();
        sell1.setName("視窗1");
        sell2.setName("視窗2");
        sell3.setName("視窗3");
        sell4.setName("視窗4");
        sell1.start();
        sell2.start();
        sell3.start();
        sell4.start();
    }
}
class Ticket extends Thread {
    private static int tickets = 100;
    @Override
    public void run() {
        while(true) {
            synchronized (Ticket.class) {//同步程式碼塊
                if(tickets <= 0) {
                    break;
                }
                try {
                    Thread.sleep(100);          //每100ms賣出一張
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName()+":買出了第"+tickets--+"張票。");
            }
        }
    }    
}

 

相關文章