摘要:本人郵箱歡迎轉(zhuǎn)載轉(zhuǎn)載請注明網(wǎng)址代碼已經(jīng)全部托管有需要的同學(xué)自行下載引言現(xiàn)在讓我們來考慮一個問題如果要讓多個線程來訪問同一份數(shù)據(jù)會發(fā)生什么現(xiàn)象呢比如的火車售票系統(tǒng)比如銀行的存取款系統(tǒng)等等都可以會出現(xiàn)多線程訪問同一個數(shù)據(jù)的情況讓我們先模擬寫一個
引言本人郵箱:
歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明網(wǎng)址 http://blog.csdn.net/tianshi_kco
github: https://github.com/kco1989/kco
代碼已經(jīng)全部托管github有需要的同學(xué)自行下載
現(xiàn)在,讓我們來考慮一個問題,如果要讓多個線程來訪問同一份數(shù)據(jù),會發(fā)生什么現(xiàn)象呢?比如12306的火車售票系統(tǒng),比如銀行的存取款系統(tǒng)等等.都可以會出現(xiàn)多線程訪問同一個數(shù)據(jù)的情況.讓我們先模擬寫一個售票系統(tǒng).
編碼
首先創(chuàng)建一個Ticket類
增加兩個成員變量count-->表示剩余的票,buyedCount-->已經(jīng)賣出的票,并提供getter方法
增加一個buyTicket方法,用來模擬售票
public class Ticket { private static final int DEFAULT_TICKET_COUNT = 1000; private int count = DEFAULT_TICKET_COUNT; //票的總數(shù) 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; } }
之后創(chuàng)建一個模擬售票的類TicketRunnable,該類的構(gòu)造器接收一個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張票, 總票數(shù)%d", Thread.currentThread().getName(), count, success ? "成功" : "失敗", ticket.getCount(),ticket.getBuyedCount(),ticket.getAllCount())); if (!success){ break; } } catch (InterruptedException e) { e.printStackTrace(); } } } }
最后創(chuàng)建一個main模擬20個售票點(diǎn)同時售票
public static void main(String[] args) throws InterruptedException { Listthreads = 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(); } }
截取某一次的部分運(yùn)行結(jié)果:
Thread-1打算買2張票,買票成功了,還剩下441張票,總共賣掉558張票, 總票數(shù)999 Thread-8打算買1張票,買票成功了,還剩下441張票,總共賣掉552張票, 總票數(shù)993 Thread-6打算買1張票,買票成功了,還剩下434張票,總共賣掉559張票, 總票數(shù)993 Thread-14打算買7張票,買票成功了,還剩下431張票,總共賣掉566張票, 總票數(shù)997 Thread-6打算買3張票,買票成功了,還剩下431張票,總共賣掉569張票, 總票數(shù)1000問題
發(fā)現(xiàn)程序運(yùn)行確實(shí)有問題
java提供了關(guān)鍵字synchronized可以保證數(shù)據(jù)同步,在Ticket的buyTicket和getter方法前加上synchronized,之后在運(yùn)行一下程序,
Thread-13打算買4張票,買票成功了,還剩下457張票,總共賣掉543張票, 總票數(shù)1000 Thread-0打算買2張票,買票成功了,還剩下479張票,總共賣掉524張票, 總票數(shù)1000 Thread-6打算買4張票,買票成功了,還剩下444張票,總共賣掉556張票, 總票數(shù)1000 Thread-0打算買9張票,買票成功了,還剩下444張票,總共賣掉556張票, 總票數(shù)1000 Thread-6打算買2張票,買票成功了,還剩下442張票,總共賣掉558張票, 總票數(shù)1000
發(fā)現(xiàn)程序沒有問題了
解決辦法在getter方法上加synchronized是因?yàn)楂@取的變量也是公共的數(shù)據(jù)
synchronized的另外一種用法是在方法體內(nèi)使用.在上述的例子中,在方法前加synchronized其實(shí)等效于synchronized(this){方法體},因?yàn)樵谏鲜龅睦又泄驳臄?shù)據(jù)就是Ticket ticket = new Ticket();這個變量,在Ticket類中就相當(dāng)與變量this
還有不使用synchronized(this){方法體}中的this也可以替換為另外一個公共的變量,如在Ticket類中定義個成員變量Object o = new Object();,然后使用synchronized(o){方法體}也可以保證數(shù)據(jù)同步.
打個比喻,比如現(xiàn)在有很多人都想進(jìn)入某一個房間的臥室(至于想干嘛,大家自己腦補(bǔ)),synchronized(對象)中的對象就是一扇門,
而synchronized就是給這扇門加鎖.那么不管這扇門是房間最外的大門,或者是臥室的門.只要所有人對這同一個門在同一個時間點(diǎn)僅僅有且只有一個能開門或者關(guān)門.那么就能保證進(jìn)入臥室的人只有一個.
這里舉個反例,比如進(jìn)入臥室有兩種渠道,一種是進(jìn)前門,一種是進(jìn)后門.(為什么臥室有前后門,肯定是有特殊用戶了,哈哈...),那么有些人對前門加鎖,另外一些人對后門加鎖.這樣就不能保證進(jìn)入臥室的人只有一個了.(悲劇說不定就這樣發(fā)生了)
在類的靜態(tài)方法加synchronized,等效于synchronized(類.class){方法體}
另外,我們也在在不修改Ticket的基礎(chǔ)上來保證售票數(shù)據(jù)的同步,只需要將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張票, 總票數(shù)%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){...}呢?不能,因?yàn)閞andom不是同一個對象,即各個線程只對自己的門加鎖,不能保證是對同一個門加鎖.
打賞如果覺得我的文章寫的還過得去的話,有錢就捧個錢場,沒錢給我捧個人場(幫我點(diǎn)贊或推薦一下)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/67064.html
摘要:線程可以調(diào)用的方法進(jìn)入阻塞,當(dāng)計數(shù)值降到時,所有之前調(diào)用阻塞的線程都會釋放。注意的初始計數(shù)值一旦降到,無法重置。 showImg(https://segmentfault.com/img/remote/1460000016012041); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 一、CountDownLatch簡介 CountDow...
時間:2017年07月09日星期日說明:本文部分內(nèi)容均來自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)源碼:無學(xué)習(xí)源碼:https://github.com/zccodere/s... 第一章:課程簡介 1-1 課程簡介 課程目標(biāo)和學(xué)習(xí)內(nèi)容 共享變量在線程間的可見性 synchronized實(shí)現(xiàn)可見性 volatile實(shí)現(xiàn)可見性 指令重排序 as-if-seria...
摘要:大多數(shù)待遇豐厚的開發(fā)職位都要求開發(fā)者精通多線程技術(shù)并且有豐富的程序開發(fā)調(diào)試優(yōu)化經(jīng)驗(yàn),所以線程相關(guān)的問題在面試中經(jīng)常會被提到。將對象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對象稱之為反序列化。 JVM 內(nèi)存溢出實(shí)例 - 實(shí)戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細(xì)介紹 Java 注解的使用,有利于學(xué)習(xí)編譯時注解 Java 程序員快速上手 Kot...
閱讀 3068·2021-11-18 10:02
閱讀 3339·2021-11-02 14:48
閱讀 3398·2019-08-30 13:52
閱讀 563·2019-08-29 17:10
閱讀 2089·2019-08-29 12:53
閱讀 1414·2019-08-29 12:53
閱讀 1035·2019-08-29 12:25
閱讀 2170·2019-08-29 12:17