你知道如何用面向對象思想寫好并發編程嗎?

在工作中,我發現很多人在設計之初都是直接按照單線程的思路來寫程序的,而忽略了本應該重視的并發問題;等上線后的某天,突然發現詭異的 Bug,再歷經千辛萬苦終于定位到問題所在,卻發現對于如何解決已經沒有了思路。

關于這個問題,我覺得咱們今天很有必要好好聊聊“如何用面向對象思想寫好并發程序”這個話題。

面向對象思想與并發編程有關系嗎?本來是沒關系的,它們分屬兩個不同的領域,但是在 Java 語言里,這兩個領域被無情地融合在一起了,好在融合的效果還是不錯的:在 Java 語言里,面向對象思想能夠讓并發編程變得更簡單。

那如何才能用面向對象思想寫好并發程序呢?結合我自己的工作經驗來看,我覺得你可以從封裝共享變量識別共享變量間的約束條件制定并發訪問策略這三個方面下手。

一、封裝共享變量

并發程序,我們關注的一個核心問題,不過是解決多線程同時訪問共享變量的問題。

面向對象思想里面有一個很重要的特性是封裝,封裝的通俗解釋就是將屬性和實現細節封裝在對象內部,外界對象只能通過目標對象提供的公共方法來間接訪問這些內部屬性,這和門票管理模型匹配度相當的高,球場里的座位就是對象屬性,球場入口就是對象的公共方法。我們把共享變量作為對象的屬性,那對于共享變量的訪問路徑就是對象的公共方法,所有入口都要安排檢票程序就相當于我們前面提到的并發訪問策略。

利用面向對象思想寫并發程序的思路,其實就這么簡單:將共享變量作為對象屬性封裝在內部,對所有公共方法制定并發訪問策略。 就拿很多統計程序都要用到計數器來說,下面的計數器程序共享變量只有一個,就是 value,我們把它作為 Counter 類的屬性,并且將兩個公共方法 get()addOne() 聲明為同步方法,這樣 Counter 類就成為一個線程安全的類了。

public class Counter {private long value;synchronized long get(){return value;}synchronized long addOne(){return ++value;}
}

當然,實際工作中,很多的場景都不會像計數器這么簡單,經常要面臨的情況往往是有很多的共享變量,例如,信用卡賬戶有卡號、姓名、身份證、信用額度、已出賬單、未出賬單等很多共享變量。這么多的共享變量,如果每一個都考慮它的并發安全問題,那我們就累死了。但其實仔細觀察,你會發現,很多共享變量的值是不會變的,例如信用卡賬戶的卡號、姓名、身份證。對于這些不會發生變化的共享變量,建議你用 final 關鍵字來修飾。 這樣既能避免并發問題,也能很明了地表明你的設計意圖,讓后面接手你程序的兄弟知道,你已經考慮過這些共享變量的并發安全問題了。

二、識別共享變量間的約束條件

識別共享變量間的約束條件非常重要。因為這些約束條件,決定了并發訪問策略。 例如,庫存管理里面有個合理庫存的概念,庫存量不能太高,也不能太低,它有一個上限和一個下限。關于這些約束條件,我們可以用下面的程序來模擬一下。在類 SafeWM 中,聲明了兩個成員變量 upperlower,分別代表庫存上限和庫存下限,這兩個變量用了 AtomicLong 這個原子類,原子類是線程安全的,所以這兩個成員變量的 set 方法就不需要同步了。

public class SafeWM {// 庫存上限private final AtomicLong upper = new AtomicLong(0);// 庫存下限private final AtomicLong lower = new AtomicLong(0);// 設置庫存上限void setUpper(long v){upper.set(v);}// 設置庫存下限void setLower(long v){lower.set(v);}// 省略其他業務代碼
}

雖說上面的代碼是沒有問題的,但是忽視了一個約束條件,就是庫存下限要小于庫存上限,這個約束條件能夠直接加到上面的 set 方法上嗎?我們先直接加一下看看效果(如下面代碼所示)。我們在 setUpper()setLower() 中增加了參數校驗,這乍看上去好像是對的,但其實存在并發問題,問題在于存在競態條件。這里我順便插一句,其實當你看到代碼里出現 if 語句的時候,就應該立刻意識到可能存在競態條件。

我們假設庫存的下限和上限分別是 (2,10),線程 A 調用 setUpper(5) 將上限設置為 5,線程 B 調用 setLower(7) 將下限設置為 7,如果線程 A 和線程 B 完全同時執行,你會發現線程 A 能夠通過參數校驗,因為這個時候,下限還沒有被線程 B 設置,還是 2,而 5>2;線程 B 也能夠通過參數校驗,因為這個時候,上限還沒有被線程 A 設置,還是 10,而 7<10。當線程 A 和線程 B 都通過參數校驗后,就把庫存的下限和上限設置成 (7, 5) 了,顯然此時的結果是不符合庫存下限要小于庫存上限這個約束條件的。

public class SafeWM {// 庫存上限private final AtomicLong upper = new AtomicLong(0);// 庫存下限private final AtomicLong lower = new AtomicLong(0);// 設置庫存上限void setUpper(long v){// 檢查參數合法性if (v < lower.get()) {throw new IllegalArgumentException();}upper.set(v);}// 設置庫存下限void setLower(long v){// 檢查參數合法性if (v > upper.get()) {throw new IllegalArgumentException();}lower.set(v);}// 省略其他業務代碼
}

在沒有識別出庫存下限要小于庫存上限這個約束條件之前,我們制定的并發訪問策略是利用原子類,但是這個策略,完全不能保證庫存下限要小于庫存上限這個約束條件。所以說,在設計階段,我們一定要識別出所有共享變量之間的約束條件,如果約束條件識別不足,很可能導致制定的并發訪問策略南轅北轍。

共享變量之間的約束條件,反映在代碼里,基本上都會有 if 語句,所以,一定要特別注意競態條件。

三、制定并發訪問策略

制定并發訪問策略,是一個非常復雜的事情。應該說整個專欄都是在嘗試搞定它。不過從方案上來看,無外乎就是以下“三件事”。

  1. 避免共享:避免共享的技術主要是利于線程本地存儲以及為每個任務分配獨立的線程。
  2. 不變模式:這個在 Java 領域應用的很少,但在其他領域卻有著廣泛的應用,例如 Actor 模式、CSP 模式以及函數式編程的基礎都是不變模式。
  3. 管程及其他同步工具:Java 領域萬能的解決方案是管程,但是對于很多特定場景,使用 Java 并發包提供的讀寫鎖、并發容器等同步工具會更好。

接下來在咱們專欄的第二模塊我會仔細講解 Java 并發工具類以及他們的應用場景,在第三模塊我還會講解并發編程的設計模式,這些都是和制定并發訪問策略有關的。

除了這些方案之外,還有一些宏觀的原則需要你了解。這些宏觀原則,有助于你寫出“健壯”的并發程序。這些原則主要有以下三條。

  1. 優先使用成熟的工具類:Java SDK 并發包里提供了豐富的工具類,基本上能滿足你日常的需要,建議你熟悉它們,用好它們,而不是自己再“發明輪子”,畢竟并發工具類不是隨隨便便就能發明成功的。
  2. 迫不得已時才使用低級的同步原語:低級的同步原語主要指的是 synchronizedLockSemaphore 等,這些雖然感覺簡單,但實際上并沒那么簡單,一定要小心使用。
  3. 避免過早優化:安全第一,并發程序首先要保證安全,出現性能瓶頸后再優化。在設計期和開發期,很多人經常會情不自禁地預估性能的瓶頸,并對此實施優化,但殘酷的現實卻是:性能瓶頸不是你想預估就能預估的。

總結

寫在最后

很多人感嘆“學習無用”,實際上之所以產生無用論,是因為自己想要的與自己所學的匹配不上,這也就意味著自己學得遠遠不夠。無論是學習還是工作,都應該有主動性,所以如果擁有大廠夢,那么就要自己努力去實現它。

以上學習資料均免費放送,最后祝愿各位身體健康,順利拿到心儀的offer!

由于文章的篇幅有限,所以這次的螞蟻金服和京東面試題答案整理在了PDF文檔里

資料獲取方式:點贊+評論我的文章,關注我,然后戳這里即可免費領取

螞蟻、京東Java崗4面:原理+索引+底層+分布式+優化等,已拿offer

螞蟻、京東Java崗4面:原理+索引+底層+分布式+優化等,已拿offer

螞蟻、京東Java崗4面:原理+索引+底層+分布式+優化等,已拿offer

CuqNXO-1623614570590)]

[外鏈圖片轉存中…(img-dlpWA0LK-1623614570592)]

[外鏈圖片轉存中…(img-mswpUISq-1623614570593)]

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/386075.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/386075.shtml
英文地址,請注明出處:http://en.pswp.cn/news/386075.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

你知道怎么在生產環境下部署tomcat嗎?

享學課堂特邀作者&#xff1a;老顧 轉載請聲明出處&#xff01; 一、前言 小伙伴們在網上看到的很多文章&#xff0c;都是對tomcat的一些介紹&#xff0c;什么配置啊&#xff0c;怎么啟動。其實在生產環境中怎么部署&#xff0c;和網上介紹的有很大區別。這篇文章老顧就帶著大…

淺談HashMap

Java集合類的整體架構 比較重要的集合類圖如下&#xff1a; 有序否 允許元素重復否 Collection 否 是 List 是 是 Set AbstractSet 否 否 HashSet TreeSet 是&#xff08;用二叉樹排序&#xff09; Map AbstractMap 否 使用 key-value 來映射和存儲數據&#xff0c; Key 必須惟…

matlab實現一元線性回歸和多元線性回歸

在回歸分析中,如果有兩個或兩個以上的自變量,就稱為多元回歸。事實上,一種現象常常是與多個因素相聯系的,由多個自變量的最優組合共同來預測或估計因變量,比只用一個自變量進行預測或估計更有效,更符合實際。 在實際經濟問題中,一個變量往往受到多個變量的影響。例如,家…

互聯網寒冬!“996”為什么還沒實行?我還等著早點下班呢!

“喊了十多個月的‘996’&#xff0c;說要實行‘996’&#xff0c;上班上到現在&#xff0c;影子都沒看到&#xff0c;我還能早點下班嗎&#xff1f;” 我一個在廣州上班的朋友小李&#xff0c;在我去廣州出差期間&#xff0c;與他聊天的時候發出了這樣的牢騷&#xff0c;我剛…

matlab求解常微分方程組/傳染病模型并繪制SIR曲線

看了很多關于傳染病模型的matlab程序,大都是繪制出兩條曲線(I、S)的,本文最大的不同是繪出SIR三條曲線。 先給出SIR微分方程組 函數文件: run的程序:

互聯網寒冬!技術站最全MySQL數據庫實戰規范

享學課堂特邀作者&#xff1a;老顧 轉載請聲明出處&#xff01; 前言 我們小伙伴們經常使用到mysql數據庫&#xff0c;一般就這么一用&#xff0c;很少會考慮mysql里面的細節問題&#xff0c;如sql語句的規范&#xff0c;或索引有沒有起到相應的效果&#xff0c;今天老顧就給大…

SQL求一個表中非重復數據及其出現的次數

mysql中&#xff0c;我們可以用distinct求不重復的數據有多少&#xff0c;也可以用group by。 這里有個例子&#xff0c;如下表sheet1&#xff0c;共有5411條數據 查詢語句 共有3446條不重復數據&#xff0c;每條不重復數據出現的次數在第二列顯示&#xff1a;

什么是微服務擴展性和高可用-可擴展性、高可用性和性能

歡迎關注專欄&#xff1a;Java架構技術進階。里面有大量batj面試題集錦&#xff0c;還有各種技術分享&#xff0c;如有好文章也歡迎投稿哦。 Overview 可擴展性、高可用性和性能 術語可擴展性、高可用性、性能和關鍵任務對于不同的組織或組織內的不同部門來說可能意味著不同的…

SQL實現當前行等于前面兩行數據之和

sql實現類似斐波那契數列的功能&#xff0c;即當前數據等于前面兩個數據之和&#xff0c;詳看本文例子 原表&#xff1a; sql語句&#xff08;此處要熟悉JION ON的用法&#xff09; 結果

【大牛系列教學】靠著這份面試題跟答案

開篇閑扯 打工人&#xff0c;打工魂&#xff0c;我們生而人上人。當“資本主義”逐漸禁錮我們人&#xff08;大&#xff09;上&#xff08;韭&#xff09;人&#xff08;菜&#xff09;肉體的時候&#xff0c;那一刻我才明白那個日不落帝國資本主義收割機瑞民族之光幸瑞幸咖啡…

matlab實現二項分布

二項分布 1. 求n次獨立重復試驗中事件A恰好發生k次的概率P。 命令:pdf 或 binopdf 格式:pdf (‘bino’, k, n, p) 或 binopdf (k, n, p) 說明:該命令的功能是計算二項分布中事件A恰好發生k次的概率。pdf為通用函數,bino表示二項分布,binopdf為專用函數,n為試驗總次數,…

【工作感悟】成功入職阿里月薪45K

前言 苦苦尋覓找工作之間&#xff0c;殊不知今日之時乃我心之痛&#xff0c;難道是我不配擁有工作嘛。自面試后他所謂的等待都過去一段時日&#xff0c;可惜在下京東上的小金庫都要見低啦。每每想到不由心中一緊。正處為難之間&#xff0c;手機忽然來了個短信預約后續面試。 我…

Mysql優化技巧

索引優化&#xff0c;查詢優化&#xff0c;查詢緩存&#xff0c;服務器設置優化&#xff0c;操作系統和硬件優化&#xff0c;應用層面優化&#xff08;web服務器&#xff0c;緩存&#xff09;等等。這里的記錄的優化技巧更適用于開發人員&#xff0c;都是從網絡上收集和自己整理…

【工作經驗分享】不會真有人覺得mybatis很難學吧

什么是自旋鎖和互斥鎖&#xff1f; 由于CLH鎖是一種自旋鎖&#xff0c;那么我們先來看看自旋鎖是什么&#xff1f; 自旋鎖說白了也是一種互斥鎖&#xff0c;只不過沒有搶到鎖的線程會一直自旋等待鎖的釋放&#xff0c;處于busy-waiting的狀態&#xff0c;此時等待鎖的線程不會…

華為面試題

技術面試&#xff1a; java&#xff1a; java學了幾年&#xff0c;看過什么書&#xff0c;有沒有看過thinking in java這本書 java的公共父類是什么&#xff0c;object類中有什么方法 object中有個hashcode方法&#xff0c;這個方法是用來做什么的&#xff0c; equal 與 的區…

【工作經驗分享】這些新技術你們都知道嗎

前言 近年來&#xff0c;微服務架構(Microservices Architecture)已經成為一種主流的軟件開發方法論&#xff0c;所謂微服務( Microservices ),就是一些具有足夠小的粒度、能夠相互協作且自治的服務體系。 微服務架構基于分布式系統&#xff0c;同時借助了面向服務架構和企業服…

京東面試題(JAVA)

京東17號一面問題與回答情況&#xff08;Java崗&#xff09; 作者&#xff1a;牛客190525號 Q1: HashMap的原理, 以及HashMap如何擴充bucket的大小 A1: 原理答上來了&#xff0c;如何擴容瞎答的&#xff0c;之前不知道擴容之后已經哈希的MapEntry如何處理&#xff0c;當時就…

【微信小程序】使用Hystrix的插件機制

前言 在本篇文章開始前&#xff0c;我想想來回答一個問題&#xff1a;我為什么要寫這一篇關于面試的文章&#xff1f; 原因有三&#xff1a;第一&#xff0c;我想為每一個為夢想時刻準備著的”有心人“盡一份自己的力量&#xff0c;提供一份高度精華的Java面試清單&#xff1…

大話數據結構——數據結構緒論

數據&#xff1a;描述客觀事物的符號&#xff0c;是計算機可以操作的對象&#xff0c;是能被計算機識別&#xff0c;并輸入給計算機處理的符號集合。 有兩個要素&#xff1a; 可以輸入到計算機中能被計算機程序處理 數據元素&#xff1a;組成數據的基本單位&#xff0c;也就…

【微信小程序】目前最全的《Java面試題及解析》

開頭 在找工作的過程中&#xff0c;對于 Redis 技術知識的掌握已經成為必須的技能。美團面試常常就會被問到Redis相關知識&#xff0c;而這次我就差點倒在了美團3面&#xff0c;面試官連問我以下幾個Redis的問題&#xff0c;然后就卡殼了… redis了解嗎&#xff1f;你說說怎么…