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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

多线程:synchronize、volatile、Lock 的区别与用法

發布時間:2025/3/21 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 多线程:synchronize、volatile、Lock 的区别与用法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Java多線程之內存可見性和原子性:Synchronized和Volatile的比較

??????在說明Java多線程內存可見性之前,先來簡單了解一下Java內存模型。

?????(1)Java所有變量都存儲在主內存中
?????(2)每個線程都有自己獨立的工作內存,里面保存該線程的使用到的變量副本(該副本就是主內存中該變量的一份拷貝)

???(1)線程對共享變量的所有操作都必須在自己的工作內存中進行,不能直接在主內存中讀寫
???(2)不同線程之間無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成。
線程1對共享變量的修改,要想被線程2及時看到,必須經過如下2個過程:


???(1)把工作內存1中更新過的共享變量刷新到主內存中
???(2)將主內存中最新的共享變量的值更新到工作內存2中


可見性與原子性


???可見性:一個線程對共享變量的修改,更夠及時的被其他線程看到
???原子性:即不可再分了,不能分為多步操作。比如賦值或者return。比如"a = 1;"和 "returna;"這樣的操作都具有原子性。類似"a +=b"這樣的操作不具有原子性,在某些JVM中"a +=b"可能要經過這樣三個步驟:
① 取出a和b
② 計算a+b
③ 將計算結果寫入內存


(1)Synchronized:保證可見性和原子性
???Synchronized能夠實現原子性和可見性;在Java內存模型中,synchronized規定,線程在加鎖→先清空工作內存→在主內存中拷貝最新變量的副本到工作內存→執行完代碼→將更改后的共享變量的值刷新到主內存中→釋放互斥鎖。


(2)Volatile:保證可見性,但不保證操作的原子性
???Volatile實現內存可見性是通過store和load指令完成的;也就是對volatile變量執行寫操作時,會在寫操作后加入一條store指令,即強迫線程將最新的值刷新到主內存中;而在讀操作時,會加入一條load指令,即強迫從主內存中讀入變量的值。但volatile不保證volatile變量的原子性,例如:

(3)Synchronized和Volatile的比較
????1)Synchronized保證內存可見性和操作的原子性

? ? ? ? ? ? ?加鎖----清空內存----在主存中拷貝最新副本----執行+修改--------刷回主存-------釋放鎖


????2)Volatile只能保證內存可見性

? ? ? ? ? a.每次讀取的時候都會CAS

? ? ? ? ?b.每次寫完都會store回主存


????3)Volatile不需要加鎖(忙等待,做自旋),比Synchronized更輕量級,并不會阻塞線程(volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。)
????4)volatile標記的變量不會被編譯器優化(通過volatile保證了有序性),而synchronized標記的變量可以被編譯器優化(如編譯器重排序的優化). (synchronized和Lock來保證有序性,很顯然,synchronized和Lock保證每個時刻是有一個線程執行同步代碼,相當于是讓線程順序執行同步代碼,自然就保證了有序性)所以根據before-happen原則,也可以保證有序性。

????5)volatile是變量修飾符,僅能用于變量,而synchronized是一個方法或塊的修飾符。
?????volatile本質是在告訴JVM當前變量在寄存器中的值是不確定的,使用前,需要先從主存中讀取,因此可以實現可見性。而對n=n+1,n++等操作時,volatile關鍵字將失效,不能起到像synchronized一樣的線程同步(原子性)的效果。
?


?Java? Synchronize?和?Lock?的區別與用法

? ? java為此也提供了2種鎖機制,synchronized和lock。

一、synchronized和lock的用法區別
?
synchronized:在需要同步的對象中加入此控制,synchronized可以加在方法上,也可以加在特定代碼塊中,括號中表示需要鎖的對象。
?
lock:需要顯示指定起始位置和終止位置。一般使用ReentrantLock類做為鎖,多個線程中必須要使用一個ReentrantLock類做為對象才能保證鎖的生效。且在加鎖和解鎖處需要通過lock()和unlock()顯示指出。所以一般會在finally塊中寫unlock()以防死鎖。
?
用法區別比較簡單,這里不贅述了,如果不懂的可以看看Java基本語法。

?

二、synchronized和lock性能區別
?
synchronized是托管給JVM執行的,而lock是java寫的控制鎖的代碼。在Java1.5中,synchronize是性能低效的。因為這是一個重量級操作,需要調用操作接口,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多

但是到了Java1.6,發生了變化。synchronized在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除(消除鎖是虛擬機另外一種鎖的優化,這種優化更徹底,Java虛擬機在JIT編譯時(可以簡單理解為當某段代碼即將第一次被執行時進行編譯,又稱即時編譯),通過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖,通過這種方式消除沒有必要的鎖,可以節省毫無意義的請求鎖時間,如下StringBuffer的append是一個同步方法,但是在add方法中的StringBuffer屬于一個局部變量,并且不會被其他線程所使用,因此StringBuffer不可能存在共享資源競爭的情景,JVM會自動將其鎖消除。),鎖粗化,輕量級鎖,偏向鎖等等。導致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他們也更支持synchronize,在未來的版本中還有優化余地。

三、synchronized和lock用途區別
?
synchronized原語和ReentrantLock在一般情況下沒有什么區別,但是在非常復雜的同步應用中,請考慮使用ReentrantLock,特別是遇到下面幾種需求的時候。

*(持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改為處理其他事情。注意:synchronized不提供中斷)
?
1.某個線程在等待一個鎖的控制權的這段時間需要中斷
2.需要分開處理一些wait-notify,ReentrantLock里面的Condition應用,能夠控制notify哪個線程(所謂notify也就是notify等待這個加鎖對象的鎖池(entryset)里面的線程
3.具有公平鎖功能,每個到來的線程都將排隊等候
?
先說第一種情況,ReentrantLock的lock機制有2種,忽略中斷鎖和響應中斷鎖,這給我們帶來了很大的靈活性。

比如:如果A、B2個線程去競爭鎖,A線程得到了鎖,B線程等待,但是A線程這個時候實在有太多事情要處理,就是一直不返回,B線程可能就會等不及了,想中斷自己,不再等待這個鎖了,轉而處理其他事情。這個時候ReentrantLock就提供了2種機制,

第一,B線程中斷自己(或者別的線程中斷它),但是ReentrantLock不去響應,繼續讓B線程等待,你再怎么中斷,我全當耳邊風(synchronized原語就是如此)(也就是說,B在我的鎖池里,B如果想要中斷自己不想等待這把鎖,那么需要經過我的響應,比如做一些引用計數的操作,才能中斷);第二,B線程中斷自己(或者別的線程中斷它),ReentrantLock處理了這個中斷,并且不再等待這個鎖的到來,完全放棄。
?
這里來做個試驗,首先搞一個Buffer類,它有讀操作和寫操作,為了不讀到臟數據,寫和讀都需要加鎖,我們先用synchronized原語來加鎖

我們期待“讀”這個線程能退出等待鎖,可是事與愿違,一旦讀這個線程發現自己得不到鎖,就一直開始等待了,就算它等死,也得不到鎖,因為寫線程要21億秒才能完成 T_T ,即使我們中斷它,它都不來響應下,看來真的要等死了。這個時候,ReentrantLock給了一種機制讓我們來響應中斷,讓“讀”能伸能屈,勇敢放棄對這個鎖的等待。我們來改寫Buffer這個類,就叫BufferInterruptibly吧,可中斷緩存。

這次“讀”線程接收到了lock.lockInterruptibly()中斷,并且有效處理了這個“異常”。

?

至于第二種情況,ReentrantLock可以與Condition的配合使用,Condition為ReentrantLock鎖的等待和釋放提供控制邏輯。
?

Lock lock = new ReentrantLock(); Condition cond = lock.newCondition();


例如,使用ReentrantLock加鎖之后,可以通過它自身的Condition.await()方法釋放該鎖,線程在此等待Condition.signal()方法,然后繼續執行下去。await方法需要放在while循環中,因此,在不同線程之間實現并發控制,還需要一個volatile的變量,boolean是原子性的變量。

調用spillDone.await()時可以釋放spillLock鎖,線程進入阻塞狀態,而等待其他線程的spillDone.signal()操作時,就會喚醒線程,重新持有spillLock鎖。
?
這里可以看出,利用lock可以使我們多線程交互變得方便,而使用synchronized則無法做到這點。



?
最后呢,ReentrantLock這個類還提供了2種競爭鎖的機制:公平鎖(先來后到原則,估計就是一個隊列性質)和非公平鎖(不分先后,估計就是一個類似于set)。

這2種機制的意思從字面上也能了解個大概:即對于多線程來說,公平鎖會依賴線程進來的順序,后進來的線程后獲得鎖。而非公平鎖的意思就是后進來的鎖也可以和前邊等待鎖的線程同時競爭鎖資源。對于效率來講,當然是非公平鎖效率更高,因為公平鎖還要判斷是不是線程隊列的第一個才會讓線程獲得鎖。

這兩種排隊策略供我們自己選擇。
?


總結

? (1)synchronized與volatile的比較

? ? ? ? ?1)volatile比synchronized更輕量級。

? ? ? ? ?2)volatile沒有synchronized使用的廣泛。

? ? ? ? ?3)volatile不需要加鎖,比synchronized更輕量級,不會阻塞線程。

? ? ? ? ?4)從內存可見性角度看,volatile讀相當于加鎖,volatile寫相當于解鎖。

? ? ? ? ?5)synchronized既能保證可見性,又能保證原子性,而volatile只能保證可見性,無法保證原子性。

? ? ? ? ?6)volatile本身不保證獲取和設置操作的原子性,僅僅保持修改的可見性。但是java的內存模型保證聲明為

volatile的long和double變量的get和set操作是原子的。

(2)補充1

? ? ? ? ?共享數據的訪問權限都必須定義為private。一般是考慮安全性,對數據提供保護,可以通過set()方法賦值,再

通過get()方法取值,這就是java封裝的思想。

? ? ? ? Java中對共享數據操作的并發控制是采用加鎖技術

? ? ? ? Java中沒有提供檢測與避免死鎖的專門機制,但應用程序員可以采用某些策略防止死鎖的發生。

? ? ? ? final也可以保證內存可見性。

?(3)補充2

? ? ? ? x86系統對64位(long、double)變量的讀寫可能不是原子操作.

volatile本身不保證獲取和設置操作的原子性,僅僅保持修改的可見性。但是java的內存模型保證聲明為volatile的long和double變量的get和set操作是原子的.兩次操作變一次操作!

? ? ? ? 因此:Java內存模型允許JVM將沒有被volatile修飾的64位數據類型的讀寫操作劃分為兩次32位的讀寫操作來運行。

? ? ? ? 導致問題:有可能會出現讀取到半個變量的情況。

? ? ? ? 解決方法:加volatile關鍵字。

? ? ? ?不過現在也有很多系統實現將沒有使用volatile的64位long\double的變量也設置為原子操作

?(4)一個問題

? ? ? ? 即使沒有保證可見性的措施,很多時候共享變量依然能夠在主內存和工作內存間得到及時的更新?

? ? ? ? ? 是的,不過要看當前線程并發量

? ? ? ? 答:一般只有在短時間內高并發的情況下才會出現變量得不到及時更新的情況,因為CPU在執行時會很快地刷新

緩存,所以一般情況下很難看到這種問題。

? ? ? ? 慢了不就不會刷新了。。。CPU運算快的話,在分配的時間片內就能完成所有工作:工作內從1->主內存->工作

內存2,然后這個線程就釋放CPU時間片,這樣一來就保證了數據的可見性。如果是慢了話CPU強行剝奪該線的資

源,分配給其它線程,該線程就需要等待CPU下次給該線程分配時間片,如果在這段時間內有別的線程訪問共享變

量,可見性就沒法保證了。

選擇比較

除非需要使用 ReentrantLock 的高級功能(中斷、公平鎖)否則優先使用 synchronized。這是因為 synchronized 是 JVM 實現的一種鎖機制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用擔心沒有釋放鎖而導致死鎖問題,因為 JVM 會確保鎖的釋放。

總結

以上是生活随笔為你收集整理的多线程:synchronize、volatile、Lock 的区别与用法的全部內容,希望文章能夠幫你解決所遇到的問題。

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