0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

JAVA最好的加鎖方法是什么

汽車玩家 ? 來源:oschina ? 作者:oschina ? 2020-05-03 17:44 ? 次閱讀

關(guān)于synchronized

眾所周知,JAVA中最簡單的加鎖方法是用關(guān)鍵字synchronized,我們可以使用這個關(guān)鍵字將一個方法變成線程安全的,也可以將一個代碼塊變成線程安全的,這樣子我們不需要再擔心多線程同時執(zhí)行到這段代碼會引發(fā)的并發(fā)問題。同時配合方法wait,notify和notifyall可以很好的實現(xiàn)多線程之間的協(xié)作,比如某個線程因為需要等待一些資源,于是調(diào)用wait方法將自己設(shè)置為waiting狀態(tài),其他線程釋放或生產(chǎn)這個線程需要的資源的時候需要通知這個線程(notify)將其喚醒,或者通知所有等待當前資源的線程(notifyall)。

然而當功能完成之后我們似乎并不滿足于此,于是我們開始考慮這么做的代價是什么,是否可以做的更好。

先說說這么做(使用synchronized)的代價是什么,當多個線程請求臨界資源的時候只能有一個線程得到滿足,那么其他的線程會做什么呢,他們會被阻塞,直到被通知(notify/notifyall)又有資源的時候才被喚醒進行再一次的鎖爭用,而后往復(fù)的是又只有一個線程能被得到滿足,其他的線程繼續(xù)進入阻塞狀態(tài),而這個時候可能會有不斷的增加爭用線程。性能損耗的關(guān)鍵點在于線程的阻塞操作是由操作系統(tǒng)來完成的,在Linux系統(tǒng)下是由pthread_mutex_lock函數(shù)來完成。線程被阻塞之后便進入了內(nèi)核調(diào)度態(tài),這個過程發(fā)生了操作系統(tǒng)將保存用戶態(tài)的上下文進入內(nèi)核態(tài),這也就是常說的上下文切換,上下文切換代價大,在于操作系統(tǒng)需要將當前線程執(zhí)行上下文內(nèi)容(包括堆棧、寄存器等存儲的內(nèi)容)的保存以便之后線程切換回來時候再進行現(xiàn)場恢復(fù)。

上面可以看出使用synchronized的代價是什么了吧,當競爭激烈的時候會引起頻繁的操作系統(tǒng)上下文切換,從而影響系統(tǒng)的性能。下面再來講講自旋鎖。

自旋鎖的原理

自旋鎖是對線程阻塞的一種優(yōu)化,他的原理簡單的說就是當線程爭用鎖失敗的時候不立即進入阻塞狀態(tài),而是再等一會,因為對于執(zhí)行時間短的代碼這一會可能就會釋放鎖,而線程就不需要進行一次阻塞與喚醒。等待操作就是讓線程多執(zhí)行幾個空指令,至于等待多久這跟具體的處理器實現(xiàn)有關(guān),也有可能處理器根本不支持自旋鎖,具體實現(xiàn)的時候我們可以設(shè)置一個臨界值,當超過了這個臨界值之后我們就不自旋了,就乖乖進入阻塞狀態(tài)吧。這種優(yōu)化對于執(zhí)行時間短的代碼是很有效的。synchronized使用自旋鎖的時機是線程進入等待隊列即阻塞的前一步。

關(guān)于偏向鎖

偏向鎖是java6提供的一種功能,主要是對無競爭條件下的對加鎖代碼執(zhí)行的優(yōu)化,得到優(yōu)化的地方是省去了對等待隊列的更新操作。在競爭條件下,獲取鎖失敗的線程會被放入等待隊列,這個隊列的更新操作是通過CAS指令來完成的。對于那么一段本部應(yīng)該被加鎖的代碼被加了鎖,我們認為每次執(zhí)行這段被加了鎖的代碼的時候更新等待隊列的操作并不是必要的,而CAS操作會延遲本地代碼的執(zhí)行,因此偏向鎖是用于優(yōu)化這個問題的。

關(guān)于Lock

Lock是JAVA5增加的內(nèi)容,在JCU(java.util.concurrent.locks)包下面,作者是并發(fā)大師Doug Lea。JCU包提供了很多封裝的鎖,包括常用的ReentrantLock和ReadWriteLock。這些所其實都是依賴java.util.concurrent.AbstractQueuedSynchronizer這個類來實現(xiàn)的,這個類有個簡寫的名字叫AQS,對這就是著名的AQS。

關(guān)于Lock,先說說線程獲取Lock鎖的時候會引起哪些事件呢。首先AQS是依賴一個被volatile修飾的int變量來標識當前鎖的狀態(tài)的,為0的時候代表當前鎖不被任何線程擁有,當線程拿到這個鎖的時候會通過CAS操作修改state的狀態(tài),那么對于爭用失敗的線程AQS會怎么辦呢,AQS內(nèi)部維護了一個等待隊列,這個隊列是純JAVA實現(xiàn)的,其實現(xiàn)也是非常巧妙的,多線程在通過CAS來獲取自己在隊列中的位置,同時隊列中的線程狀態(tài)也是阻塞狀態(tài),遇到阻塞就頭疼了,上面已經(jīng)介紹過阻塞會帶來的性能問題。在源碼中我們可以看到的是AQS通過LockSupport(LockSupport底層依賴Unsafe)將線程阻塞,關(guān)于LockSupport我有一篇文章介紹的,其功能是用來代替wait和notity/notifyall的,更好的地方是LockSupport對park方法和unpark方法的調(diào)用沒有先后的限制,而notify/notifyall必須在wait調(diào)用之后調(diào)用。盡管如此,這一切并沒有阻止線程進入阻塞狀態(tài),我有點失望。

無鎖時代

講到無鎖,必然是Disruptor并發(fā)框架,Disruptor底層依賴一個RingBuffer來進行線程之間的數(shù)據(jù)交換,無鎖在于在并發(fā)條件下,多線程對RingBuffer的讀和寫不會涉及到鎖,然而因為RingBuffer滿或者RingBuffer中沒有可消費內(nèi)容引發(fā)的線程等待,那就要另當別論了。簡單幾句介紹下無鎖原理,RingBuffer維護者可讀和可寫的指針,也叫游標,它指向生產(chǎn)者或消費者需要寫或讀的位置,而對于指針的更新是由CAS來完成的,這個過程中我們不需要加鎖/解鎖的過程。

后記:

JAVA鎖方面的知識主要是要搞清楚不同的鎖的優(yōu)點與缺點,深入到操作系統(tǒng)層的實現(xiàn)機制與不同場景中對應(yīng)用的性能影響。本文簡單的擼了一下JAVA鎖從synchronized到無鎖的發(fā)展以及一些鎖的簡單原理,主要是拋磚引玉吧,因為介紹的比較簡單,對于文中提到的知識不知道的同學可以深入了解,我相信你會很有收獲。有些實現(xiàn)的原理介紹可能就一句話,但是實際實現(xiàn)起來是蠻復(fù)雜的,需要考慮到的東西是我們沒有寫過所不能考慮到的。到這里,如果你的項目中用到了多線程并發(fā),你是否會考慮使用無鎖模型來優(yōu)化你項目中多線程之間的通信呢。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 寄存器
    +關(guān)注

    關(guān)注

    31

    文章

    5295

    瀏覽量

    119824
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2952

    瀏覽量

    104489
收藏 人收藏

    評論

    相關(guān)推薦

    ESP32會不會有多線程問題,需要加鎖嗎?

    ESP32會不會有多線程問題,需要加鎖
    發(fā)表于 07-19 08:05

    華納云:java web和java有什么區(qū)別java web和java有什么區(qū)別

    Java Web和Java是兩個不同的概念,它們在功能、用途和實現(xiàn)方式上存在一些區(qū)別,下面將詳細介紹它們之間的區(qū)別。 1. 功能和用途: – Java是一種編程語言,它提供了一種用于開發(fā)各種應(yīng)用程序
    的頭像 發(fā)表于 07-16 13:35 ?619次閱讀
    華納云:<b class='flag-5'>java</b> web和<b class='flag-5'>java</b>有什么區(qū)別<b class='flag-5'>java</b> web和<b class='flag-5'>java</b>有什么區(qū)別

    java環(huán)境配置成功后怎么運行

    Java環(huán)境配置成功后,我們可以使用幾種方式來運行Java程序。下面將詳細介紹這幾種方式以及其使用方法。 命令行運行方式 在成功配置Java環(huán)境后,我們可以通過命令行來運行
    的頭像 發(fā)表于 12-06 15:57 ?1929次閱讀

    idea如何運行main方法

    運行main方法是指在Java程序中執(zhí)行main方法來啟動程序 在Java編程中,main方法是每個Ja
    的頭像 發(fā)表于 12-06 14:58 ?3476次閱讀

    java虛擬機內(nèi)存包括遠空間內(nèi)存嗎

    Java虛擬機(JVM)內(nèi)存是Java程序執(zhí)行時所使用的內(nèi)存空間的總稱,包括了Java堆、方法區(qū)、本地方法棧、虛擬機棧和程序計數(shù)器等多個部分
    的頭像 發(fā)表于 12-05 14:15 ?367次閱讀

    java的主類必須是public類嗎

    一下Java程序的基本結(jié)構(gòu)。一個Java程序可以包含多個類,但是只能有一個公共的類作為主類。這個主類必須包含一個特殊的方法,即main方法,它是程序的入口點。當程序被執(zhí)行時,首先執(zhí)行m
    的頭像 發(fā)表于 11-28 16:15 ?5322次閱讀

    簡單了解Java的新特性

    Java 8 到 Java 20,Java 已經(jīng)走過了漫長的道路,自 Java 8 以來,Java 生態(tài)系統(tǒng)發(fā)生了很多變化。最顯著的變化是
    的頭像 發(fā)表于 11-23 16:38 ?1051次閱讀
    簡單了解<b class='flag-5'>Java</b>的新特性

    如何查看java程序的內(nèi)存分布

    要查看Java程序的內(nèi)存分布,首先需要了解Java程序運行時的內(nèi)存模型。 Java程序的內(nèi)存分布可以分為以下幾個部分:程序計數(shù)器、Java虛擬機棧、本地
    的頭像 發(fā)表于 11-23 14:47 ?987次閱讀

    java內(nèi)存溢出排查方法

    模型。Java內(nèi)存模型分為線程棧、堆、方法區(qū)(Java 8之前稱為永久代,Java 8后稱為元空間)和本地方法
    的頭像 發(fā)表于 11-23 14:46 ?3036次閱讀

    C語言加鎖沒有鎖住是什么原因?

    C語言加鎖沒有鎖住是什么原因? C語言中的鎖機制主要是通過線程庫提供的函數(shù)來實現(xiàn)的,主要用于多線程編程中的資源同步與互斥。加鎖的目的是確保同一時刻只有一個線程可以訪問某個共享資源,以防止數(shù)據(jù)競爭
    的頭像 發(fā)表于 11-22 17:41 ?587次閱讀

    java抽象類可以有普通方法

    Java中的抽象類可以有普通方法,但它也可以有抽象方法。抽象類是一種中間狀態(tài),介于普通類和接口之間。它允許定義方法的具體實現(xiàn),同時也可以定義一些沒有具體實現(xiàn)的抽象
    的頭像 發(fā)表于 11-21 10:22 ?1436次閱讀

    java中的equals是什么意思

    Java中,equals是一個用于比較對象是否相等的方法。該方法來自于Object類,所有的Java類都繼承了該方法。equals
    的頭像 發(fā)表于 11-17 17:07 ?2601次閱讀

    java中equals()方法的注意事項

    Java中的equals()方法是用于比較兩個對象是否相等的方法。這個方法是在Object類中定義的,因此所有的Java類都默認地繼承了eq
    的頭像 發(fā)表于 11-17 16:59 ?769次閱讀

    java的equals怎么用

    Java的equals()方法是Object類中的方法,用于比較對象的內(nèi)容是否相等。在默認情況下,equals()方法比較的是對象的引用,即判斷兩個對象是否是同一個實例。但是在很多情況
    的頭像 發(fā)表于 11-17 16:56 ?662次閱讀

    Java中 equals 方法的實現(xiàn)方式

    Java 中 equals 方法用于比較兩個對象是否相等。在默認情況下,Object 類中的 equals 方法實現(xiàn)是比較兩個對象的引用是否相等,即比較內(nèi)存地址是否相同。然而,對于自定義對象,在很多
    的頭像 發(fā)表于 11-17 16:53 ?928次閱讀