日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

关于解决并发问题,99%的程序员都会忽略的一个重要方案!

發布時間:2025/3/16 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 关于解决并发问题,99%的程序员都会忽略的一个重要方案! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

△Hollis, 一個對Coding有著獨特追求的人△

這是Hollis的第?370?篇原創分享

作者 l zyz1992

來源 l Hollis(ID:hollischuang)

在并發編程的世界里,共享變量的線程安全問題永遠是一個無法避免且不得不面對的問題,如果只有讀的情況,那么永遠也不會出現線程安全的問題,因為多線程讀永遠是線程安全的,但是多線程讀寫一定會存在線程安全的問題。

那既然這么說是不是通過只讀就能解決并發問題呢?其實最簡單的辦法就是讓共享變量只有讀操作,而沒有寫操作。這個辦法如此重要,以至于被上升到了一種解決并發問題的設計模式:不變性(Immutability)模式

所謂不變性,簡單來講,就是對象一旦被創建之后,狀態就不再發生變化。換句話說,就是變量一旦被賦值,就不允許修改了(沒有寫操作);沒有修改操作,也就是保持了不變性。

1、不可變性的類

在 java 中,如果要實現一個不可變的對象是很簡單的,將其定義為 final 即可,同樣類也是如此,只需要通過 final 來修飾某個類即可。同時將一個類所有的屬性都設置成 final 的,并且只允許存在只讀方法,那么這個類基本上就具備不可變性了。

更嚴格的做法是這個類本身也是 final 的,也就是不允許繼承。因為子類可以覆蓋父類的方法,有可能改變不可變性,所以推薦你在實際工作中,使用這種更嚴格的做法。

我們在日常開發中,已經在不知不覺中享受不可變模式帶來的好處,例如經常用到的 String、Long、Integer、Double 等基礎類型的包裝類都具備不可變性,這些對象的線程安全性都是靠不可變性來保證的。

仔細翻看這些類的聲明、屬性和方法,你會發現它們都嚴格遵守不可變類的三點要求:類是 final 的,屬性也是 final 的。同樣的一旦某個類被 final 修飾,其本身就不能被繼承了,也就無法重寫其方法,即方法是只讀的。

既然說方法是只讀的,但是 Java 的 String 方法也有類似字符替換操作,這個不就已經改變了value[] 變量了嗎?因為 value[] 是這么定義的。

我們結合 String 的源代碼(jdk8)來看一下 jdk 是如何處理這個問題的,下面是源碼的截圖

它實際上是重新定義了一個新的 buf[] 來保存數據,這樣在最后返回數據的時候確實沒有修改 原始的value[],而是將替換后的字符串作為返回值返回了。

通過分析 String 的實現,你可能已經發現了,如果具備不可變性的類,需要提供類似修改的功能,具體該怎么操作呢?做法很簡單,那就是創建一個新的不可變對象,這是與可變對象的一個重要區別,可變對象往往是修改自己的屬性。

所有的修改操作都創建一個新的不可變對象。但是一個問題的解決必然會帶來的新的問題,那就是這樣勢必在每次使用的時候都會創建新的對象,那豈不是無端的降低了系統的性能了浪費了系統的資源?這個時候享元模式就可以大顯神通了。

2、享元模式避免創建重復對象

享元模式你可能實際開發中使用的很少,它是這么定義的:

享元模式(Flyweight Pattern):是一種軟件設計模式。它使用共享物件,用來盡可能減少內存使用量以及分享資訊給盡可能多的相似物件;它適合用于只是因重復而導致使用無法令人接受的大量內存的大量物件。

通常物件中的部分狀態是可以分享。常見做法是把它們放在外部數據結構,當需要使用時再將它們傳遞給享元

看不懂沒關系,用一句直白話來概括就是:通過對象池的技術來避免重復的創建對象。這就好比是 Spring 中的容器(單例模式下),我們的對象都交給 Spring 容器來管理,這樣我們再使用的時候只需要到容器中去拿即可,而不是每次都去創建新的對象。

利用享元模式可以減少創建對象的數量,從而減少內存占用。Java 語言里面 Long、Integer、Short、Byte等這些基本數據類型的包裝類都用到了享元模式。

享元模式本質上其實就是一個對象池,利用享元模式創建對象的邏輯也很簡單:創建之前,首先去對象池里看看是不是存在;如果已經存在,就利用對象池里的對象;如果不存在,就會新創建一個對象,并且把這個新創建出來的對象放進對象池里

jdk 源碼中是如何使用享元模式的呢?我們以 Long 這個類為例來解釋說明下。

Long 這個類并沒有照搬享元模式,Long 內部維護了一個靜態的對象池,僅緩存了[-128,127]之間的數字,這個對象池在 JVM 啟動的時候就創建好了,而且這個對象池一直都不會變化,也就是說它是靜態的。之所以采用這樣的設計,是因為 Long 這個對象的狀態共有 264 種,實在太多,不宜全部緩存,而[-128,127]之間的數字利用率最高。

下面的示例代碼出自 Java 1.8,valueOf() 方法就用到了 LongCache 這個緩存,你可以結合著來加深理解。

在看下 LongCache 中的 cache 方法(關鍵地方都在圖片的注釋中了)

3、基本類型包裝類作為鎖對象

正是由于這些包裝類內部用了享元模式,所以基本上所有的基礎類型的包裝類都不適合做鎖,因為它們內部用到了享元模式,這會導致看上去私有的鎖,其實是共有的。看下下面的代碼,我們假設以 Long 對象作為鎖,

class?A?{//定義一個?A?對象名字叫?aObj?,值為?1private?Long?aObj?=?Long.valueOf(1);//定義一個?B?對象名字叫?bObj,值為?1private?Long?bObj?=?Long.valueOf(1);private?void?a()?{//鎖對象是?aObjsynchronized?(aObj)?{System.out.println("正在執行A方法,5秒以后退出");try?{TimeUnit.SECONDS.sleep(5);System.out.println("A執行結束......");}?catch?(InterruptedException?e)?{e.printStackTrace();}}}private?void?b()?{//鎖對象是?bObjsynchronized?(bObj)?{System.out.println("正在執行B方法,2秒以后退出");try?{TimeUnit.SECONDS.sleep(2);System.out.println("B執行結束......");}?catch?(InterruptedException?e)?{e.printStackTrace();}}}public?static?void?main(String[]?args)?throws?InterruptedException?{A?a?=?new?A();//開通兩個線程來執行,因為aObj?和?bObj?是不同的對象,所以理論上應該是互不干擾的new?Thread(a::a).start();TimeUnit.SECONDS.sleep(1);new?Thread(a::b).start();}}
?

但是卻出現了上面這樣的結果?為什么會是同步的執行呢?就是因為享元模式導致的,因為 1 是在 [-128~127] 的,所以定義再多的對象都是直接從緩存池中拿的,并不會創建新的對象,即鎖的是同一個對象。現在改成一個不在 [-128~127] 范圍之內的,假設是128

結果為:

這個時候發現兩個是互不干擾的,也就是兩個鎖并不是同一個對象

4、 使用 Immutability 模式的注意事項

在使用 Immutability 模式的時候,需要注意以下兩點:

  • 對象的所有屬性都是 final 的,并不能保證不可變性;

  • 不可變對象也需要正確發布。

  • 在 Java 語言中,final 修飾的屬性一旦被賦值,就不可以再修改,但是如果屬性的類型是普通對象,那么這個普通對象的屬性是可以被修改的。什么鬼?亂七八糟的。別急,我們來看個例子(畢竟光說含義就是等于在耍流氓)。

    class?D?{final?C?c;public?D(C?c)?{this.c?=?c;}private?void?changeValue(int?salary)?{c.setSalary(salary);}public?static?void?main(String[]?args)?{C?c?=?new?C();c.setSalary(1);System.out.println("c.getSalary()?=?"?+?c.getSalary());D?d?=?new?D(c);d.changeValue(3);System.out.println("c.getSalary()?=?"?+?c.getSalary());}}

    在使用 Immutability 模式的時候一定要確認保持不變性的邊界在哪里,是否要求屬性對象也具備不可變性。這里的C對象是不可變的,但是里面的屬性卻是可以修改的。如果想要屬性也不可以被修改,那么屬性也必須要定義為 final 的。像這樣的臨界問題在處理的時候一定要加倍小心。

    5、本文小結

    利用 Immutability 模式解決并發問題,也許你覺得有點陌生,其實你天天都在享受它的戰果。Java 語言里面的 String 和 Long、Integer、Double 等基礎類型的包裝類都具備不可變性,這些對象的線程安全性都是靠不可變性來保證的。Immutability 模式是最簡單的解決并發問題的方法,建議當你試圖解決一個并發問題時,可以首先嘗試一下 Immutability 模式,看是否能夠快速解決。

    具備不變性的對象,只有一種狀態,這個狀態由對象內部所有的不變屬性共同決定。其實還有一種更簡單的不變性對象,那就是無狀態。無狀態對象內部沒有屬性,只有方法。除了無狀態的對象,你可能還聽說過無狀態的服務、無狀態的協議等等。無狀態有很多好處,最核心的一點就是性能。在多線程領域,無狀態對象沒有線程安全問題,無需同步處理,自然性能很好;在分布式領域,無狀態意味著可以無限地水平擴展,所以分布式領域里面性能的瓶頸一定不是出在無狀態的服務節點上。

    ?

    技術交流群

    最近有很多人問,有沒有讀者交流群,想知道怎么加入。

    最近我創建了一些群,大家可以加入。交流群都是免費的,只需要大家加入之后不要隨便發廣告,多多交流技術就好了。

    目前創建了多個交流群,全國交流群、北上廣杭深等各地區交流群、面試交流群、資源共享群等。

    有興趣入群的同學,可長按掃描下方二維碼,一定要備注:全國 Or 城市 Or 面試 Or 資源,根據格式備注,可更快被通過且邀請進群。

    ▲長按掃描

    往期推薦

    居然有人提問“國家何時整治程序員的高薪現象”?


    再見了,谷歌


    Windows 11 再惹“眾怒”!網友:微軟就是逼我去買新電腦!

    如果你喜歡本文,

    請長按二維碼,關注?Hollis.

    轉發至朋友圈,是對我最大的支持。

    點個?在看?

    喜歡是一種感覺

    在看是一種支持

    ↘↘↘

    總結

    以上是生活随笔為你收集整理的关于解决并发问题,99%的程序员都会忽略的一个重要方案!的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。