Java通過賣票理解多線程
以賣票的例子來介紹多線程和資源共享,下面我們來看看為什么要用賣票作為例子。
賣票是包含一系列動作的過程,有各種操作,例如查詢票、收錢、數(shù)錢、出票等,其中有一個操作是每次賣掉一張,就將總的票數(shù)減去1。有10張票,如果一個人賣票,先做查票、收錢、數(shù)錢等各種操作,再將總的票數(shù)減去1,效率很低。如果多個人賣票,每個人都是做同樣的操作,數(shù)錢、檢查錢,最后將總的票數(shù)減1,這樣效率高。但是有一個問題,如果出現(xiàn)兩個人同時將總的票數(shù)減掉了1,例如,A、B兩個人同時讀取到票的總數(shù)是10,A從中減去1,同時B也從中減去1,總數(shù)顯示是9,其實票只有8張。導(dǎo)致數(shù)據(jù)錯誤。
按照正常邏輯,同一時刻只允許一個人來從總票數(shù)中減去1,A讀取總票數(shù),再減去1的過程中,B必須等待,等A操作完了,B才能進行。其實票就是共享資源,一次只能由一個人訪問。這里就要用到同步機制,即鎖機制,使用關(guān)鍵詞synchronized將讀取總的票數(shù),并減去1的操作鎖定,使得一次只能由一個人訪問。每個售票員就是一個線程,多個售票員進行同一項賣票任務(wù)。
synchronized原理是,執(zhí)行synchronized部分代碼的時候必須需要對象鎖,而一個對象只有一個鎖,只有執(zhí)行完synchronized里面的代碼后釋放鎖,其他線程才可以獲得鎖,那么就保證了同一時刻只有一個線程訪問synchronized里面的代碼。使得資源共享的關(guān)鍵是,只有一個實例,synchronized使用的是同一把鎖,用實例的鎖或者定義一個實例。這就需要使用實現(xiàn)Runnable接口的方式,實現(xiàn)多線程,這樣傳入的是一個實例。繼承Thread的方式,傳入的是多個實例,每個實例都有一個鎖,那就無法實現(xiàn)控制。
具體代碼如下:
package com.test;
public class SaleTickets implements Runnable
{
private int ticketCount = 10;// 總的票數(shù),這個是共享資源,多個線程都會訪問
Object mutex = new Object();// 鎖,自己定義的,或者使用實例的鎖
/**
* 賣票
*/
public void sellTicket()
{
synchronized (mutex)// 當(dāng)操作的是共享數(shù)據(jù)時,
// 用同步代碼塊進行包圍起來,執(zhí)行里面的代碼需要mutex的鎖,但是mutex只有一個鎖。這樣在執(zhí)行時,只能有一個線程執(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();//只定義了一個實例,這就只有一個Object mutex = new Object();即一個鎖。
Thread th1 = new Thread(runTicekt, "窗口1");//每個線程等其他線程釋放該鎖后,才能執(zhí)行
Thread th2 = new Thread(runTicekt, "窗口2");
Thread th3 = new Thread(runTicekt, "窗口3");
Thread th4 = new Thread(runTicekt, "窗口4");
th1.start();
th2.start();
th3.start();
th4.start();
}
}
輸出:
窗口1正在賣票,還剩9張票 窗口4正在賣票,還剩8張票 窗口3正在賣票,還剩7張票 窗口2正在賣票,還剩6張票 窗口3正在賣票,還剩5張票 窗口2正在賣票,還剩4張票 窗口1正在賣票,還剩3張票 窗口4正在賣票,還剩2張票 窗口3正在賣票,還剩1張票 窗口1正在賣票,還剩0張票 票已經(jīng)賣完!
這是多個線程,完成同一個任務(wù)的情況,即多個線程調(diào)用同一個實例,通過實現(xiàn)Runable接口實現(xiàn)。多個線程可以異步的做這個任務(wù)中其他事情,但是對于共享資源的訪問只能以同步的方式操作,即一個接一個訪問共享資源,其他資源可以并行訪問。
另一種實現(xiàn)多線程的方式是繼承Thread,調(diào)用的時候需要傳遞多個實例,這是多個線程,多個實例的情況,每個線程獨立處理一個實例,各個線程不能實現(xiàn)資源共享。
總結(jié)
以上是本文關(guān)于通過賣票實例理解多線程的全部內(nèi)容,希望對大家有所幫助。
相關(guān)文章
MyBatis XML方式的基本用法之多表查詢功能的示例代碼
這篇文章主要介紹了MyBatis XML方式的基本用法之多表查詢功能的示例代碼,本文通過示例文字相結(jié)合的形式給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-07-07
Java中String字符串轉(zhuǎn)具體對象的幾種常用方式
String對象可以用來存儲任何字符串類型的數(shù)據(jù),包括HTML、XML等格式的字符串,下面這篇文章主要給大家介紹了關(guān)于JavaString字符串轉(zhuǎn)具體對象的幾種常用方式,需要的朋友可以參考下2024-03-03
詳解Spring Data操作Redis數(shù)據(jù)庫
Redis是一種NOSQL數(shù)據(jù)庫,Key-Value形式對數(shù)據(jù)進行存儲,其中數(shù)據(jù)可以以內(nèi)存形式存在,也可以持久化到文件系統(tǒng)。Spring data對Redis進行了很好的封裝,用起來也是十分的得心應(yīng)手,接下來通過本文給大家分享Spring Data操作Redis數(shù)據(jù)庫,需要的朋友參考下2017-03-03
深入學(xué)習(xí)Java單元測試(Junit+Mock+代碼覆蓋率)
在做單元測試時,代碼覆蓋率常常被拿來作為衡量測試好壞的指標(biāo),甚至,用代碼覆蓋率來考核測試任務(wù)完成情況,比如,代碼覆蓋率必須達到80%或 90%。下面我們就來詳細學(xué)習(xí)下java單元測試吧2019-06-06

