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