看我在項(xiàng)目里怎么用設(shè)計(jì)模式,這么學(xué)設(shè)計(jì)模式也太簡(jiǎn)單了
大家好,今天給大家分享一個(gè)寫代碼的設(shè)計(jì)模式,就是我們最最耳熟能詳?shù)膯卫O(shè)計(jì)模式。
可能很多人都聽說過這個(gè)單例設(shè)計(jì)模式了,甚至都寫的賊溜,但是今天給大家說說用這個(gè)單例設(shè)計(jì)模式,咱們是怎么把代碼的性能大幅度提升的,單例模式跟代碼性能的關(guān)系,恐怕很多兄弟還沒認(rèn)真研究過呢!
一次請(qǐng)求執(zhí)行流程
首先我們先來看看什么叫做單例模式,要理解單例模式,我們就得先說說不用單例模式的時(shí)候,我們平時(shí)創(chuàng)建對(duì)象是怎么弄的。
平時(shí)創(chuàng)建對(duì)象這個(gè)簡(jiǎn)單吧,比如我們搞一個(gè)對(duì)外的 web 接口,然后再接口收到一個(gè)請(qǐng)求的時(shí)候,就創(chuàng)建一個(gè)對(duì)象。
這個(gè)偽代碼如下:
上面那段代碼極為的簡(jiǎn)單,假設(shè)你有一個(gè) Controller 對(duì)外提供一個(gè) http 接口,然后每次你通過瀏覽器發(fā)送一個(gè)創(chuàng)建用戶的請(qǐng)求。
也就是針對(duì)/user/create 這個(gè) url 的請(qǐng)求,發(fā)送一個(gè) CreateUserRequest 請(qǐng)求參數(shù),代碼里就會(huì)通過 new 關(guān)鍵字,搞出來一個(gè) User 對(duì)象。
然后再通過new關(guān)鍵字創(chuàng)建一個(gè) UserService 組件來,接著把 User 對(duì)象交給 UserService 組件去插入這個(gè)用戶數(shù)據(jù)到數(shù)據(jù)庫(kù)里去,這段代碼基本但凡是懂 java 的應(yīng)該都能看懂。
但是這里有一個(gè)問題,大家知道每次處理請(qǐng)求的時(shí)候,這段代碼運(yùn)行他會(huì)干什么事情嗎?
其實(shí)有一個(gè)最關(guān)鍵的點(diǎn)就是,他每次請(qǐng)求過來都會(huì)在內(nèi)存里創(chuàng)建一個(gè) User 對(duì)象和一個(gè) UserService 對(duì)象,那這些對(duì)象是如何創(chuàng)建的呢?
java 代碼是如何運(yùn)行的?
下面就得給大家來揭秘一下這個(gè)代碼運(yùn)行的底層原理了,首先呢,當(dāng)我們啟動(dòng)一個(gè) Java 程序的時(shí)候,一定會(huì)啟動(dòng)一個(gè) JVM 進(jìn)程。
比如說上面那段代碼,你可能是通過 SpringBoot 這類框架用 main 方法啟動(dòng)的,也可能是把他打包以后放到 Tomcat 里去運(yùn)行的。
如果你是直接運(yùn)行 main 方法來啟動(dòng)的,那么就會(huì)直接啟動(dòng)一個(gè) JVM 進(jìn)程,如果你是把代碼打包以后放 Tomcat 里運(yùn)行的,那么 Tomcat 自己本身就是一個(gè) JVM 進(jìn)程。
如下圖:
接著呢,其實(shí)你啟動(dòng)的 JVM 進(jìn)程,會(huì)把你寫好的代碼加載到內(nèi)存里來然后運(yùn)行你寫的代碼,你的代碼運(yùn)行起來以后,他就可以干你希望他干的事情了,比如說接收瀏覽器發(fā)送的 http 請(qǐng)求,然后創(chuàng)建一些對(duì)象,插入數(shù)據(jù)庫(kù)等等。
如下圖所示:
那么這個(gè)時(shí)候,有一個(gè)很關(guān)鍵的點(diǎn),就是你的代碼運(yùn)行的時(shí)候用 new User() 和 new UserService() 創(chuàng)建出來的對(duì)象扔哪兒去了?
很簡(jiǎn)單,你的 JVM 進(jìn)程是有一塊自己的內(nèi)存區(qū)域可以用的,而且就他可以用,這塊區(qū)域叫做堆內(nèi)存。
這就類似于咱們自己家蓋個(gè)小別墅,弄一塊院子自己可以在里面種花種草一樣,別人不能在你家院子里種黃瓜和大蒜,對(duì)不對(duì)?
如下圖:
那么接著呢,上面我們寫的那段代碼,大家注意一下,每次收到一個(gè)請(qǐng)求,都會(huì)創(chuàng)建一個(gè) User 對(duì)象和一個(gè) UserService 對(duì)象,對(duì)不對(duì)?
所以說,隨著你不停的發(fā)送請(qǐng)求不停的發(fā)送請(qǐng)求,咱們的代碼是不是會(huì)不停的創(chuàng)建對(duì)象不停的創(chuàng)建對(duì)象,然后咱們的堆內(nèi)存里,對(duì)象是不是就會(huì)變的越來越多,越來越多?
如下圖:
堆內(nèi)存滿了后怎么辦?
那么我問大家一個(gè)問題,堆內(nèi)存是一塊內(nèi)存空間,他是可以無限制的一直放入對(duì)象的嗎?
當(dāng)然不是了,當(dāng)你的對(duì)象越來越多,太多的時(shí)候,就會(huì)把這塊內(nèi)存空間給塞滿,塞滿了以后他就放不下新的對(duì)象了,這個(gè)時(shí)候怎么辦呢?
他會(huì)觸發(fā)一個(gè)垃圾回收的動(dòng)作,就是 JVM 進(jìn)程自己偷偷摸摸開了一個(gè)垃圾回收線程,這個(gè)線程就專門盯著我們的堆內(nèi)存,感覺他快滿了,就把里面的對(duì)象清理掉一部分,這就叫做垃圾回收。
如下圖:
但是每次垃圾回收都有一個(gè)問題,他因?yàn)橐謇淼粢恍?duì)象,所以往往會(huì)在清理對(duì)象的時(shí)候,避免你再創(chuàng)建新的對(duì)象了。
不然就跟你媽媽打掃你的房間一樣,人家一邊在打掃垃圾,結(jié)果你還不停的吃東西往地下扔垃圾,你媽媽不打你屁股才怪,對(duì)吧?所以一般垃圾回收的時(shí)候,會(huì)讓 JVM 進(jìn)程停止工作,別創(chuàng)建新的對(duì)象了。
如下圖:
那么在垃圾回收進(jìn)行中,JVM 進(jìn)程停止運(yùn)行的這個(gè)期間,是不是會(huì)導(dǎo)致一個(gè)問題,那就是你的用戶發(fā)送過來的請(qǐng)求就沒人處理了。
沒錯(cuò),這個(gè)時(shí)候用戶會(huì)感覺每次發(fā)送請(qǐng)求那是卡住,一直卡著沒有返回,此時(shí)系統(tǒng)性能是處于一個(gè)極差的狀態(tài)的。
如下圖:
用單例模式如何優(yōu)化系統(tǒng)性能呢?
那么這個(gè)時(shí)候問題來了,回到這篇文章的主體,就是用單例模式如何優(yōu)化系統(tǒng)性能呢?
其實(shí)針對(duì)上面的問題,很多小伙伴可能已經(jīng)發(fā)現(xiàn)了,如果想要優(yōu)化系統(tǒng)性能,有一個(gè)關(guān)鍵的點(diǎn)就是盡量創(chuàng)建少一些的對(duì)象,避免堆內(nèi)存頻繁的塞滿,也就可以避免頻繁的垃圾回收,更可以避免頻繁的 JVM 進(jìn)程停頓,進(jìn)而避免系統(tǒng)請(qǐng)求頻繁的卡頓無響應(yīng)。
那如何少創(chuàng)建一些對(duì)象呢?單例模式就是一個(gè)很好的辦法了,對(duì)于我們來說,其實(shí)完全可以讓 UserService 這個(gè)對(duì)象就只創(chuàng)建一次,不要每次請(qǐng)求重復(fù)的創(chuàng)建他。
讓一個(gè)對(duì)象就創(chuàng)建一次,就是單例模式,單例模式有很多種寫法,其中一種寫法如下:
大家可以看到上面的代碼,我們?cè)?UserService 中定義了一個(gè)私有化的靜態(tài)內(nèi)部類 Singleton,在 Singleton 里定義了一個(gè)靜態(tài)變量 UserService 對(duì)象。
這樣的話,Singleton 這個(gè)類只會(huì)被加載一次,只有類加載的時(shí)候才會(huì)實(shí)例化一個(gè)靜態(tài)變量 UserService 對(duì)象,后續(xù)每次通過 getInstance() 方法都是直接獲取這唯一一個(gè)對(duì)象就可以了,不會(huì)重復(fù)創(chuàng)建對(duì)象。
這就是單例模式的一種寫法,也是企業(yè)開發(fā)中最常用的一種寫法,用了單例模式后,就可以大幅度降低我們創(chuàng)建的對(duì)象數(shù)量,避免堆內(nèi)存頻繁塞滿,頻繁垃圾回收,頻繁 JVM 進(jìn)程停頓影響請(qǐng)求性能,這樣往往可以幫助我們更好的提升系統(tǒng)的性能。