不過時的技術
最近兩天被極客時間的新課刷群刷屏。刷屏的標題大多是“學了這麼多年 Java,卻連 singleton 都不會用”、“面試總被問高併發,你真的會麼”這一類標題黨。內容千篇一律是推薦極客時間打新的課程,《Java 併發程式設計實戰》。
高併發哥又不是沒做過,隨手找了一下,發現陳皓在 2009 年的一篇文章就提到了正確的解法,以及背後的原因。《深入淺出單例項 SINGLETON 設計模式 》。
文中給出幾種功能上正確的 singleton 寫法。
// version 1.4 public class Singleton { private volatile static Singleton singleton = null; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton== null) { singleton= new Singleton(); } } } return singleton; } }
請留意私有變數的描述詞volatile
,目的是不讓編譯器對指令進行重排序優化。
// version 1.5 public class Singleton { private volatile static Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance() { return singleton; } }
這是自動載入版本。每次載入類的時候,例項就生成了。所以載入類的過程可能會很慢(特別是有很多繼承、引用的情況)。
// version 1.6 public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() {} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
這是對上面 1.5 版本的修正。SingletonHolder
是個私有類,並且在Singleton
載入的時候才會被呼叫,INSTANCE
才會被真正建立。
這段程式碼是即確保了執行緒安全,又實現了懶載入的較優辦法。
還有一個所謂最優(優雅?程式碼最少?)的辦法,不過不建議大家使用,可讀性實在不太高。有點奇技淫巧的意思,大大犧牲了程式碼的可讀性。
public enum Singleton{ INSTANCE; }
利用了enum
的建立是執行緒安全這一特性。
PS:PHP 中沒有 singleton 的困擾,因為 php 語言特點決定的。php-fpm 本身就是 accepter-worker 併發模式,程式設計師寫的 PHP 程式其實只是 worker,worker 與 worker 之間由 fpm 完成資源隔離和協調,PHP 程式設計師並不需要從記憶體資料的層面考慮併發的情況。所以有句話講得不錯,singleton 在 PHP 語言中不是一個好實踐(practice) 。PHP 的 singleton 用簡單的工廠模式就夠了。
今天抽空找了下 MQTT 的 QoS2 實現方式,記錄如下。原科普文連結《MQTT QoS 深度解讀 》。
無論是 QoS2 還是 transaction,原理都是一樣的:通過一次代價非常小、成功概率足夠高的操作,作為最後確認的依據 。這樣做並不是說絕對不出錯,而是出錯的概率足夠低,實踐中可以忽略。
sequenceDiagram participant Publisher participant Broker participant Subscriber Publisher->>Publisher: Store(Msg) Publisher->>Broker: PUBLISH(QoS2, Msg) Broker->>Broker: Store(Msg) Broker->>Publisher: PUBREC Publisher->>Broker: PUBREL Broker->>Subscriber: PUBLISH(QoS2, Msg) Broker->>Publisher: PUBCOMP Publisher->>Publisher: Delete(Msg) Subscriber->>Subscriber: Store(Msg) Subscriber->>Broker: PUBREC Broker->>Subscriber: PUBREL Subscriber->>Subscriber: Notify(Msg) Subscriber->>Broker: PUBCOMP Broker->>Broker: Delete(Msg) Subscriber->>Subscriber: Delete(Msg)
簡單一點的模型,如果不需要中間的 broker,則流程如下。
sequenceDiagram participant Publisher participant Subscriber Publisher->>Publisher: Store(Msg) Publisher->>Subscriber: (1) PUBLISH(QoS2, Msg) Subscriber->>Subscriber: Store(Msg) Subscriber->>Publisher: (2) PUBREC Publisher->>Subscriber: (3) PUBREL Subscriber->>Subscriber: Notify(Msg) Subscriber->>Publisher: (4) PUBCOMP Subscriber->>Subscriber: Delete(Msg) Publisher->>Publisher: Delete(Msg)
從簡化以後的模型可以看到,publisher 和 subscriber 有兩次互動。第一次,publisher 把 msg 推送給 subscriber,對應PUBLISH
/PUBREC
指令。第二次,publisher 等於是詢問 subscriber,“你是不是收到一次”,對應PUBREL
/PUBCOMP
指令。
如果沒有第 (3)/(4)步,PUBREL
/PUBCOMP
指令,實際就是 QoS1,至少收到一次
。
再少一點,如果沒有 (2)/(3)/(4) 步,只剩第 (1) 步,實際就是 QoS0,至多隻傳送一次 。
科普文裡面提問,為什麼 MQTT QoS2 是兩次“握手”,而不是像 TCP 一樣,三次握手。我覺得這個問題太教條了。為什麼 negotiate 就一定要想到 TCP 呢?當然,如果一定要回答,最本質的區別就是,MQTT QoS2 通訊是單向的,而 TCP 連線的通訊是雙向的。單向的只需要一方取信於另外一方即可,而雙向通訊需要兩方都取信於對方。