以賣票的例子來介紹多線程和資源共享,下面我們來看看為什么要用賣票作為例子。
成都創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供托里網(wǎng)站建設(shè)、托里做網(wǎng)站、托里網(wǎng)站設(shè)計(jì)、托里網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、托里企業(yè)網(wǎng)站模板建站服務(wù),10年托里做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
賣票是包含一系列動(dòng)作的過程,有各種操作,例如查詢票、收錢、數(shù)錢、出票等,其中有一個(gè)操作是每次賣掉一張,就將總的票數(shù)減去1。有10張票,如果一個(gè)人賣票,先做查票、收錢、數(shù)錢等各種操作,再將總的票數(shù)減去1,效率很低。如果多個(gè)人賣票,每個(gè)人都是做同樣的操作,數(shù)錢、檢查錢,最后將總的票數(shù)減1,這樣效率高。但是有一個(gè)問題,如果出現(xiàn)兩個(gè)人同時(shí)將總的票數(shù)減掉了1,例如,A、B兩個(gè)人同時(shí)讀取到票的總數(shù)是10,A從中減去1,同時(shí)B也從中減去1,總數(shù)顯示是9,其實(shí)票只有8張。導(dǎo)致數(shù)據(jù)錯(cuò)誤。
按照正常邏輯,同一時(shí)刻只允許一個(gè)人來從總票數(shù)中減去1,A讀取總票數(shù),再減去1的過程中,B必須等待,等A操作完了,B才能進(jìn)行。其實(shí)票就是共享資源,一次只能由一個(gè)人訪問。這里就要用到同步機(jī)制,即鎖機(jī)制,使用關(guān)鍵詞synchronized將讀取總的票數(shù),并減去1的操作鎖定,使得一次只能由一個(gè)人訪問。每個(gè)售票員就是一個(gè)線程,多個(gè)售票員進(jìn)行同一項(xiàng)賣票任務(wù)。
synchronized原理是,執(zhí)行synchronized部分代碼的時(shí)候必須需要對象鎖,而一個(gè)對象只有一個(gè)鎖,只有執(zhí)行完synchronized里面的代碼后釋放鎖,其他線程才可以獲得鎖,那么就保證了同一時(shí)刻只有一個(gè)線程訪問synchronized里面的代碼。使得資源共享的關(guān)鍵是,只有一個(gè)實(shí)例,synchronized使用的是同一把鎖,用實(shí)例的鎖或者定義一個(gè)實(shí)例。這就需要使用實(shí)現(xiàn)Runnable接口的方式,實(shí)現(xiàn)多線程,這樣傳入的是一個(gè)實(shí)例。繼承Thread的方式,傳入的是多個(gè)實(shí)例,每個(gè)實(shí)例都有一個(gè)鎖,那就無法實(shí)現(xiàn)控制。
具體代碼如下:
package com.test; public class SaleTickets implements Runnable { private int ticketCount = 10;// 總的票數(shù),這個(gè)是共享資源,多個(gè)線程都會訪問 Object mutex = new Object();// 鎖,自己定義的,或者使用實(shí)例的鎖 /** * 賣票 */ public void sellTicket() { synchronized (mutex)// 當(dāng)操作的是共享數(shù)據(jù)時(shí), // 用同步代碼塊進(jìn)行包圍起來,執(zhí)行里面的代碼需要mutex的鎖,但是mutex只有一個(gè)鎖。這樣在執(zhí)行時(shí),只能有一個(gè)線程執(zhí)行同步代碼塊里面的內(nèi)容 { if (ticketCount > 0) { ticketCount--; System.out.println(Thread.currentThread().getName() + "正在賣票,還剩" + ticketCount + "張票"); } else { System.out.println("票已經(jīng)賣完!"); return; } } } public void run() { while (ticketCount > 0)// 循環(huán)是指線程不停的去賣票 { sellTicket(); /** * 在同步代碼塊里面睡覺,和不睡效果是一樣 的,作用只是自已不執(zhí)行,也不讓線程執(zhí)行。sleep不釋放鎖,抱著鎖睡覺。其他線程拿不到鎖,也不能執(zhí)行同步代碼。wait()可以釋放鎖 * 所以把睡覺放到同步代碼塊的外面,這樣賣完一張票就睡一會,讓其他線程再賣,這樣所有的線程都可以賣票 */ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
下面是調(diào)用:
package com.test; public class TicketMain { public static void main(String[] args) { SaleTickets runTicekt = new SaleTickets();//只定義了一個(gè)實(shí)例,這就只有一個(gè)Object mutex = new Object();即一個(gè)鎖。 Thread th2 = new Thread(runTicekt, "窗口1");//每個(gè)線程等其他線程釋放該鎖后,才能執(zhí)行 Thread th3 = new Thread(runTicekt, "窗口2"); Thread th4 = new Thread(runTicekt, "窗口3"); Thread th5 = new Thread(runTicekt, "窗口4"); th2.start(); th3.start(); th4.start(); th5.start(); } }
輸出:
窗口1正在賣票,還剩9張票 窗口4正在賣票,還剩8張票 窗口3正在賣票,還剩7張票 窗口2正在賣票,還剩6張票 窗口3正在賣票,還剩5張票 窗口2正在賣票,還剩4張票 窗口1正在賣票,還剩3張票 窗口4正在賣票,還剩2張票 窗口3正在賣票,還剩1張票 窗口1正在賣票,還剩0張票 票已經(jīng)賣完!
這是多個(gè)線程,完成同一個(gè)任務(wù)的情況,即多個(gè)線程調(diào)用同一個(gè)實(shí)例,通過實(shí)現(xiàn)Runable接口實(shí)現(xiàn)。多個(gè)線程可以異步的做這個(gè)任務(wù)中其他事情,但是對于共享資源的訪問只能以同步的方式操作,即一個(gè)接一個(gè)訪問共享資源,其他資源可以并行訪問。
另一種實(shí)現(xiàn)多線程的方式是繼承Thread,調(diào)用的時(shí)候需要傳遞多個(gè)實(shí)例,這是多個(gè)線程,多個(gè)實(shí)例的情況,每個(gè)線程獨(dú)立處理一個(gè)實(shí)例,各個(gè)線程不能實(shí)現(xiàn)資源共享。
總結(jié)
以上是本文關(guān)于通過賣票實(shí)例理解多線程的全部內(nèi)容,希望對大家有所幫助。