詳解對象池模式 & 解釋器模式
設(shè)計模式系列文章之前已經(jīng)跟大家聊了一大半了,但是都是聊一些比較常見的設(shè)計模式,接下來我們主要是聊一些不常見的設(shè)計模式。
不常見的設(shè)計模式不意味則就可以不用了解,不用知道。
每一種設(shè)計模式都是為了處理一種業(yè)務(wù)場景,或者解決某種問題而存在的,還是那句話,存在即合理。
學(xué)習(xí)設(shè)計模式優(yōu)點:
- 提升查看框架源碼的能力
- 提升自己對復(fù)雜業(yè)務(wù)的邏輯的代碼設(shè)計能力以及code能力
- 對面試以及后面的職場道路打下扎實的基礎(chǔ)
大綱
對象池模式
在面試的時候,經(jīng)常會有一些面試官會問一些針對線上一些機器出現(xiàn)OOM 時應(yīng)該怎么去排查
現(xiàn)在比較常見的是第一種查看機器是否配置了自動生成Dump文件
查看方法一般則是進入機器的:/home/www/XXXXX(項目名)/bin 目錄下會有一個 setenv.sh 文件
cat一下文件觀察是否配置:
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:HeapDumpPath=/tmp/heapdump.hprof
第二種就是自己offline出問題的機器自己dump一下文件。
然后開始用一些分析文件工具比如(MAT)等查看問題
頻繁的實例化對象,是很耗費資源的,同時也會頻繁的觸發(fā)GC操作,所以在處理一些封裝外部資源的對象時就應(yīng)該合理的去規(guī)避這一點。
解決方案這就是重用和共享這些創(chuàng)建成本高昂的對象,這就是對象池模式,也理解為池化技術(shù)。
結(jié)構(gòu)圖如下:
- ResourcePool(資源池類):用于封裝邏輯的類,即用來保存和管理資源列表
- Resource(資源類):用于封裝特定的資源類,資源類被資源池類飲用,所以只要資源池沒有被重新分配,所以它們就永遠不會被回收。
- Client(請求客戶端):使用資源的類
以上結(jié)構(gòu)定義來之設(shè)計模式之美
通過結(jié)構(gòu)圖其實可以理解為,事先在機器內(nèi)存中開辟一塊內(nèi)存,用來事先存儲需要定義好的對象,當有需要的時候從內(nèi)存獲取一個,不用了之后再返回回去,來實現(xiàn)的一個池化技術(shù)。
對象池模式舉例
假設(shè)現(xiàn)在朋友A想買車了,但是現(xiàn)在又沒有那么多錢,只能找同學(xué)B借錢同時承諾只要兩個月后就會還錢。
同學(xué)B的存款是固定的,假設(shè)這就是資源池,那么朋友A就是請求客戶端了。那么代碼的實現(xiàn)就如下所示:
- public class Money {
- // 狀態(tài)是否是被借出去
- private Integer status;
- // 金額單位 W
- private Integer money;
- public Integer getStatus() {
- return status;
- }
- public void setStatus(Integer status) {
- this.status = status;
- }
- public Integer getMoney() {
- return money;
- }
- public void setMoney(Integer money) {
- this.money = money;
- }
- }
定義一個資源類(money),里面有一個狀態(tài)標志當前對象是否是被占用,這個是重點
- public class ClassmateBPool {
- // 對象池大小 可以理解為理想情況下自己最多可以借2W
- public static int numObjects = 2;
- // 對象池最大大小 可以理解為自己全部家當最多可以借出去錢
- public static int maxObjects = 5;
- // 存放對象池中對象的向量(Money)
- protected Vector<Money> objectsPool = null;
- public ClassmateBPool() {
- }
- /**
- * 初始化對象池
- */
- public synchronized void createPool() {
- // 確保對象池沒有創(chuàng)建。如果創(chuàng)建了,保存對象 objectsPool 不會為空
- if (objectsPool != null) {
- System.out.println("對象池已經(jīng)創(chuàng)建");
- return;
- }
- // 創(chuàng)建對象池,添加資源
- objectsPool = new Vector<Money>();
- for (int i = 0; i < numObjects; i++) {
- objectsPool.add(create());
- }
- System.out.println("對象池開始創(chuàng)建");
- }
- public synchronized Money getMoney() {
- int times = 1;
- // 基本判斷
- if (objectsPool == null) {
- throw new RuntimeException("未創(chuàng)建對象池");
- }
- Money money = null;
- // 獲得一個可用的對象
- money = getFreeMoney();
- // money==null ,證明當前沒有可以借出去的錢了,則開始從另外的銀行卡轉(zhuǎn)錢過來
- while (money == null) {
- // 開始擴充對象池
- createNewMoney(2);
- // 擴充完了之后再去獲取空閑的金額
- money = getFreeMoney();
- if (money != null) {
- break;
- }
- // 重試次數(shù)
- times++;
- System.out.println("重試次數(shù):"+times);
- if (times > 3) {
- throw new RuntimeException("當前沒有空閑的金額");
- }
- }
- System.out.println("借錢"+ JSON.toJSONString(money));
- // 返回獲得的可用的對象
- return money;
- }
- /**
- * 返回金額
- */
- public void returnMoney(Money money) {
- // 確保對象池存在,如果對象沒有創(chuàng)建(不存在),直接返回
- if (objectsPool == null) {
- return;
- }
- Iterator<Money> iterator = objectsPool.iterator();
- while (iterator.hasNext()) {
- Money returnMoney = iterator.next();
- if (money == returnMoney) {
- money.setStatus(0);
- System.out.println("還錢成功");
- break;
- }
- }
- }
- /**
- *擴充資源池
- */
- public void createNewMoney(int increment) {
- for (int i = 0; i < increment; i++) {
- if (objectsPool.size() > maxObjects) {
- return;
- }
- objectsPool.add(create());
- }
- }
- /**
- *查詢空閑金額
- */
- private Money getFreeMoney() {
- Iterator<Money> iterator = objectsPool.iterator();
- while (iterator.hasNext()) {
- Money money = iterator.next();
- // 判斷是否是被占用
- if (money.getStatus() == 0) {
- money.setStatus(1);
- return money;
- }
- }
- return null;
- }
- public Money create() {
- Money money = new Money();
- money.setMoney(1);
- // 0 未被占用,1 占用
- money.setStatus(0);
- return money;
- }
- }
創(chuàng)建資源池類,里面包含了 初始化資源池方法createPool,以及獲取空閑金額getMoney方法,以及returnMoney歸還金額方法。
- public class ClientTest {
- public static void main(String[] args) {
- ClassmateBPool classmateBPool = new ClassmateBPool();
- // 初始化連接池
- classmateBPool.createPool();
- // 借錢
- Money money = classmateBPool.getMoney();
- //還錢
- classmateBPool.returnMoney(money);
- // result:對象池開始創(chuàng)建
- // 借錢{"money":1,"status":1}
- // 還錢成功
- // 模擬10的請求
- for (int i = 0; i < 10; i++) {
- Money money1 = classmateBPool.getMoney();
- }
- //result:
- //對象池開始創(chuàng)建
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //重試次數(shù):2
- //重試次數(shù):3
- //重試次數(shù):4
- //Exception in thread "main" java.lang.RuntimeException:當前沒有空閑的金額
- // 模擬10的請求,同時在第3次第4次的是時候還錢了
- for (int i = 0; i < 10; i++) {
- Money money1 = classmateBPool.getMoney();
- if (i == 3 || i == 4) {
- classmateBPool.returnMoney(money1);
- }
- }
- //result:
- // 對象池開始創(chuàng)建
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //還錢成功
- //借錢{"money":1,"status":1}
- //還錢成功
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //借錢{"money":1,"status":1}
- //重試次數(shù):2
- //重試次數(shù):3
- //重試次數(shù):4
- //Exception in thread "main" java.lang.RuntimeException:當前沒有空閑的金額
- }
- }
最后就是測試demo了,分別用了一次請求,10次請求,以及有歸還的場景下。
以上就是對象池模式定義以及舉例代碼實現(xiàn)
針對這種池化技術(shù)比較常見于C3P0、DBCP、Proxool等連接池,但是也有一個通用的工具common-pool包里面的感興趣的同學(xué)可以通過這個demo之后再去看下源碼,對比一下自己是否理解對象池模式。
- 對象池模式的優(yōu)點:
- 能夠重復(fù)使用對象池的對象,較少了對象的創(chuàng)建,回收以及內(nèi)存等消耗。
- 缺點:
- 需要額外的開辟內(nèi)存空間,而且這個內(nèi)存大小,以及對象數(shù)量不好把控等。
小結(jié)
針對Java當前對象的分配操作也不是很慢了,而且這種業(yè)務(wù)場景我也基本沒有遇到所以沒辦法給大家找到合適的業(yè)務(wù)代碼舉例。
大家可以作為了解的形式去理解一下,方便我們?nèi)タ匆恍┰创a或者一些業(yè)務(wù)場景提供一定的思考。
下面給大家分享第二種不出常見的模式解釋器模式
解釋器模式
大家在寫正則表達式的時候不知道有沒有思考過一個問題,Java它是怎么解析我們寫的這個表達式語法呢?
不清的同學(xué)就看看這接下來的解釋器模式
解釋器模式定義:
- GOF中的定義:解釋器模式為某個語言定義它的語法(或者叫文法)表示,并定義一個解釋器用來處理這個語法。
說白了就是定義一種規(guī)則,通過這種規(guī)則去解析按照這種規(guī)則編寫的句子。
有點繞?還是看下結(jié)構(gòu)圖吧:
- Context(環(huán)境):用于封裝全局信息,所有具體的解釋器都是訪問Context
- AbstraceExpression(抽象表達式):抽象接口類,聲明解釋器的具體方法
- TerminalExpression(終結(jié)符表達式):一種解釋器類,實現(xiàn)與語法的終結(jié)符相關(guān)操作。
- NonTerminalExpression(非終結(jié)符表達式):實現(xiàn)語法的不同規(guī)則或符號的類。每一種語法都對應(yīng)著一個這種類
- 以上結(jié)構(gòu)圖定義來之設(shè)計模式之美
舉例
看上面的結(jié)構(gòu)圖以及定義我估計很多同學(xué)還是沒有理解,那我們還是來個實際一點的例子好了
假設(shè)現(xiàn)在要我們實現(xiàn)小學(xué)數(shù)學(xué)里面的加減乘除運算,輸入任意的表達式能準確的得到結(jié)果
比如:(6➕6)✖️3 或者 2✖️3➕6
當然上面這種列子我們直接輸入肯定是不能實現(xiàn)的,得按照需要的語法規(guī)則才行所以:
- (6➕6)✖️3 輸入的語法可以理解為 6 6 ➕ 3 ✖️
- 2✖️3➕6 輸入的語法可以理解為 2 3 ✖️ 6 ➕
以(6➕6)✖️3為例整個運算過程,如圖所示
話不多說,直接開始擼代碼了。
- // 定義抽象表達式
- public interface Expression {
- Long interpret();
- }
- // 乘法表達式
- public class MultiplyExpression implements Expression {
- Expression left;
- Expression right;
- public MultiplyExpression(Expression left, Expression right) {
- this.left = left;
- this.right = right;
- }
- @Override
- public Long interpret() {
- return left.interpret() * right.interpret();
- }
- }
- // 數(shù)字表達式
- public class NumberExpression implements Expression {
- private Long number;
- public NumberExpression(Long number) {
- this.number = number;
- }
- @Override
- public Long interpret() {
- return number;
- }
- }
上面很簡單就是定義一個抽象表達式接口,聲明解釋器的具體方法interpret,同時定義不同的語法NonTerminalExpression
- public class Context {
- private Deque<Expression> numbers = new LinkedList<>();
- public Long ExpressionInterpreter(String expression) {
- Long result = null;
- for (String ex : expression.split(" ")) {
- if (isOperator(ex)) {
- Expression exp = null;
- if (ex.equals("+")) {
- exp = new AddExpression(numbers.pollFirst(), numbers.pollFirst());
- }
- if (ex.equals("*")) {
- exp = new MultiplyExpression(numbers.pollFirst(), numbers.pollFirst());
- }
- // 當還有其他的運算符時,接著在這里添加就行了
- if (null != exp) {
- result = exp.interpret();
- numbers.addFirst(new NumberExpression(result));
- }
- }
- if (isNumber(ex)) {
- numbers.addLast(new NumberExpression(Long.parseLong(ex)));
- }
- }
- return result;
- }
- // 判斷是否是運算符號,這里舉例只用了➕和✖️,可以擴展更多的其它運算符
- private boolean isOperator(String ex) {
- return ex.equals("+") || ex.equals("*");
- }
- // 判斷是否是數(shù)字
- private boolean isNumber(String ex) {
- return ex.chars().allMatch(Character::isDigit);
- }
- }
定義環(huán)境具體的運算規(guī)則,需要注意的是后續(xù)如果有更多的運算符號只需接著在后面添加就可以了。
這我們是用的Deque作為整個運算的順序鏈表,可以用其他的方式比如Stack等。
- public class ExpressionPatternDemo {
- public static void main(String[] args) {
- Context context = new Context();
- Long result = context.ExpressionInterpreter("6 6 + 3 *");
- System.out.println(result);
- // result : 36
- Context contextTwo = new Context();
- Long resultTwo = contextTwo.ExpressionInterpreter("2 3 * 6 +");
- System.out.println(resultTwo);
- // result : 12
- }
- }
最后就是來看我們的測試demo了。
按照我們輸入的語法規(guī)則,解釋出我們想要的結(jié)果,這就是解釋器模式。
因為解釋器模式我們本身接觸很少,大家作為一個了解就可以了,更多的是運用在表達式,或者規(guī)則引擎等地方。
感興趣的伙伴可以再去看看Pattern.compile的源碼,本質(zhì)也是用的解釋器模式
總結(jié)
針對這些不怎么常見,或者在業(yè)務(wù)代碼中不怎么常見的模式只能是跟大家分享一下它的原理以及應(yīng)用場景,大家可以作為了解的形式來理解它,方便在以后遇到的問題,能有一個好的思路。
學(xué)習(xí)的越多,了解的越多對我們本身是沒有壞處的。
本次的分享到這里就結(jié)束了,我是敖丙,你知道的越多,你不知道的越多,我們下期見!