(二)java多線程之synchronized

本人郵箱: kco1989@qq.com
歡迎轉載,轉載請註明網址 http://blog.csdn.net/tianshi_kco
github: https://github.com/kco1989/kco
代碼已經所有託管github有須要的同窗自行下載git

引言

如今,讓咱們來考慮一個問題,若是要讓多個線程來訪問同一份數據,會發生什麼現象呢?好比12306的火車售票系統,好比銀行的存取款系統等等.均可以會出現多線程訪問同一個數據的狀況.讓咱們先模擬寫一個售票系統.github

編碼

  • 首先建立一個Ticket
    • 增長兩個成員變量count-->表示剩餘的票,buyedCount-->已經賣出的票,並提供getter方法
    • 增長一個buyTicket方法,用來模擬售票
public class Ticket {
    private static final int DEFAULT_TICKET_COUNT = 1000;
    private int count = DEFAULT_TICKET_COUNT; //票的總數
    private int buyedCount = 0;

    public boolean buyTicket(int count) throws InterruptedException {
            if (this.count - count < 0){
                Thread.sleep(10);
                return false;
            }else{
                this.count = this.count - count;
                Thread.sleep(1);
                this.buyedCount = this.buyedCount + count;
                return true;
            }
    }

    public int getCount() {
        return count;
    }

    public int getBuyedCount() {
        return buyedCount;
    }

    public int getAllCount(){
        return count + buyedCount;
    }
}
  • 以後建立一個模擬售票的類TicketRunnable,該類的構造器接收一個Ticket
public static class TicketRunnable implements Runnable{
    private Ticket ticket;
    private Random random;
    public TicketRunnable(Ticket ticket) {
        this.ticket = ticket;
        random = new Random();
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i ++){
            try {
                int count =  random.nextInt(10) + 1;
                boolean success = ticket.buyTicket(count);
                System.out.println(String.format("%s打算買%d張票,買票%s了,還剩下%d張票,總共賣掉%d張票, 總票數%d",
                        Thread.currentThread().getName(), count, success ? "成功" : "失敗",
                        ticket.getCount(),ticket.getBuyedCount(),ticket.getAllCount()));
                if (!success){
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}
  • 最後建立一個main模擬20個售票點同時售票
public static void main(String[] args) throws InterruptedException {
    List<Thread> threads = new ArrayList<>();
    Ticket ticket = new Ticket();
    for (int i = 0; i < 20; i ++){
        threads.add(new Thread(new TicketRunnable(ticket)));
    }

    for (Thread thread : threads){
        thread.start();
    }
}
  • 截取某一次的部分運行結果:
Thread-1打算買2張票,買票成功了,還剩下441張票,總共賣掉558張票, 總票數999
Thread-8打算買1張票,買票成功了,還剩下441張票,總共賣掉552張票, 總票數993
Thread-6打算買1張票,買票成功了,還剩下434張票,總共賣掉559張票, 總票數993
Thread-14打算買7張票,買票成功了,還剩下431張票,總共賣掉566張票, 總票數997
Thread-6打算買3張票,買票成功了,還剩下431張票,總共賣掉569張票, 總票數1000

問題

發現程序運行確實有問題微信

java提供了關鍵字synchronized能夠保證數據同步,在TicketbuyTicketgetter方法前加上synchronized,以後在運行一下程序,多線程

Thread-13打算買4張票,買票成功了,還剩下457張票,總共賣掉543張票, 總票數1000
Thread-0打算買2張票,買票成功了,還剩下479張票,總共賣掉524張票, 總票數1000
Thread-6打算買4張票,買票成功了,還剩下444張票,總共賣掉556張票, 總票數1000
Thread-0打算買9張票,買票成功了,還剩下444張票,總共賣掉556張票, 總票數1000
Thread-6打算買2張票,買票成功了,還剩下442張票,總共賣掉558張票, 總票數1000

發現程序沒有問題了dom

getter方法上加synchronized是由於獲取的變量也是公共的數據ide

解決辦法

synchronized的另一種用法是在方法體內使用.在上述的例子中,在方法前加synchronized其實等效於synchronized(this){方法體},由於在上述的例子中公共的數據就是Ticket ticket = new Ticket();這個變量,在Ticket類中就至關與變量thisthis

還有不使用synchronized(this){方法體}中的this也能夠替換爲另一個公共的變量,如在Ticket類中定義個成員變量Object o = new Object();,而後使用synchronized(o){方法體}也能夠保證數據同步.編碼

打個比喻,好比如今有不少人都想進入某一個房間的臥室(至於想幹嗎,你們本身腦補),synchronized(對象)中的對象就是一扇門,
synchronized就是給這扇門加鎖.那麼無論這扇門是房間最外的大門,或者是臥室的門.只要全部人對這同一個門在同一個時間點僅僅有且只有一個能開門或者關門.那麼就能保證進入臥室的人只有一個.
這裏舉個反例,好比進入臥室有兩種渠道,一種是進前門,一種是進後門.(爲何臥室有先後門,確定是有特殊用戶了,哈哈...),那麼有些人對前門加鎖,另一些人對後門加鎖.這樣就不能保證進入臥室的人只有一個了.(悲劇說不定就這樣發生了).net

在類的靜態方法加synchronized,等效於synchronized(類.class){方法體}

另外,咱們也在在不修改Ticket的基礎上來保證售票數據的同步,只須要將TicketRunnable.run方法改成

public void run() {
    for (int i = 0; i < 5; i ++){
        synchronized (ticket){
            try {
                int count =  random.nextInt(10) + 1;
                boolean success = ticket.buyTicket(count);
                System.out.println(String.format("%s打算買%d張票,買票%s了,還剩下%d張票,總共賣掉%d張票, 總票數%d",
                        Thread.currentThread().getName(), count, success ? "成功" : "失敗",
                        ticket.getCount(),ticket.getBuyedCount(),ticket.getAllCount()));
                if (!success){
                    break;
                }
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

這樣也能保證售票正常,那在這裏能不能把synchronized (ticket){...}改成synchronized (random){...}呢?不能,由於random不是同一個對象,即各個線程只對本身的門加鎖,不能保證是對同一個門加鎖.


打賞

若是以爲個人文章寫的還過得去的話,有錢就捧個錢場,沒錢給我捧我的場(幫我點贊或推薦一下)
微信打賞
支付寶打賞

相關文章
相關標籤/搜索