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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

volatile与synchronized 同步原理基础讲解

發(fā)布時間:2023/12/19 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 volatile与synchronized 同步原理基础讲解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

基本知識:

可見性:

  可見性是一種復(fù)雜的屬性,因?yàn)榭梢娦灾械腻e誤總是會違背我們的直覺。通常,我們無法確保執(zhí)行讀操作的線程能適時地看到其他線程寫入的值,有時甚至是根本不可能的事情。為了確保多個線程之間對內(nèi)存寫入操作的可見性,必須使用同步機(jī)制。

  可見性,是指線程之間的可見性,一個線程修改的狀態(tài)對另一個線程是可見的。也就是一個線程修改的結(jié)果。另一個線程馬上就能看到。比如:用volatile修飾的變量,就會具有可見性。volatile修飾的變量不允許線程內(nèi)部緩存和重排序,即直接修改內(nèi)存。所以對其他線程是可見的。但是這里需要注意一個問題,volatile只能讓被他修飾內(nèi)容具有可見性,但不能保證它具有原子性。比如 volatile int a = 0;之后有一個操作 a++;這個變量a具有可見性,但是a++ 依然是一個非原子操作,也就是這個操作同樣存在線程安全問題。

  在 Java 中 volatile、synchronized 和 final 實(shí)現(xiàn)可見性。

原子性:

  原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型) 這個操作是不可分割的,那么我們說這個操作時原子操作。再比如:a++; 這個操作實(shí)際是a = a + 1;是可分割的,所以他不是一個原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(shù)(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那么我們稱它具有原子性。java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

  在 Java 中 synchronized 和在 lock、unlock 中操作保證原子性。

有序性:

  Java 語言提供了 volatile 和 synchronized 兩個關(guān)鍵字來保證線程之間操作的有序性,volatile 是因?yàn)槠浔旧戆敖怪噶钪嘏判颉钡恼Z義,synchronized 是由“一個變量在同一個時刻只允許一條線程對其進(jìn)行 lock 操作”這條規(guī)則獲得的,此規(guī)則決定了持有同一個對象鎖的兩個同步塊只能串行執(zhí)行。

下面內(nèi)容摘錄自《Java Concurrency in Practice》:

  下面一段代碼在多線程環(huán)境下,將存在問題。

+ View code 1 /** 2 * @author zhengbinMac 3 */ 4 public class NoVisibility { 5 private static boolean ready; 6 private static int number; 7 private static class ReaderThread extends Thread { 8 @Override 9 public void run() { 10 while(!ready) { 11 Thread.yield(); 12 } 13 System.out.println(number); 14 } 15 } 16 public static void main(String[] args) { 17 new ReaderThread().start(); 18 number = 42; 19 ready = true; 20 } 21 }

  NoVisibility可能會持續(xù)循環(huán)下去,因?yàn)樽x線程可能永遠(yuǎn)都看不到ready的值。甚至NoVisibility可能會輸出0,因?yàn)樽x線程可能看到了寫入ready的值,但卻沒有看到之后寫入number的值,這種現(xiàn)象被稱為“重排序”。只要在某個線程中無法檢測到重排序情況(即使在其他線程中可以明顯地看到該線程中的重排序),那么就無法確保線程中的操作將按照程序中指定的順序來執(zhí)行。當(dāng)主線程首先寫入number,然后在沒有同步的情況下寫入ready,那么讀線程看到的順序可能與寫入的順序完全相反。

  在沒有同步的情況下,編譯器、處理器以及運(yùn)行時等都可能對操作的執(zhí)行順序進(jìn)行一些意想不到的調(diào)整。在缺乏足夠同步的多線程程序中,要想對內(nèi)存操作的執(zhí)行春旭進(jìn)行判斷,無法得到正確的結(jié)論。

  這個看上去像是一個失敗的設(shè)計(jì),但卻能使JVM充分地利用現(xiàn)代多核處理器的強(qiáng)大性能。例如,在缺少同步的情況下,Java內(nèi)存模型允許編譯器對操作順序進(jìn)行重排序,并將數(shù)值緩存在寄存器中。此外,它還允許CPU對操作順序進(jìn)行重排序,并將數(shù)值緩存在處理器特定的緩存中。

Volatile原理

  Java語言提供了一種稍弱的同步機(jī)制,即volatile變量,用來確保將變量的更新操作通知到其他線程。當(dāng)把變量聲明為volatile類型后,編譯器與運(yùn)行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內(nèi)存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。

  在訪問volatile變量時不會執(zhí)行加鎖操作,因此也就不會使執(zhí)行線程阻塞,因此volatile變量是一種比sychronized關(guān)鍵字更輕量級的同步機(jī)制。

  當(dāng)對非 volatile 變量進(jìn)行讀寫的時候,每個線程先從內(nèi)存拷貝變量到CPU緩存中。如果計(jì)算機(jī)有多個CPU,每個線程可能在不同的CPU上被處理,這意味著每個線程可以拷貝到不同的 CPU cache 中。

  而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內(nèi)存中讀,跳過 CPU cache 這一步。

當(dāng)一個變量定義為 volatile 之后,將具備兩種特性:

  1.保證此變量對所有的線程的可見性,這里的“可見性”,如本文開頭所述,當(dāng)一個線程修改了這個變量的值,volatile 保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新。但普通變量做不到這點(diǎn),普通變量的值在線程間傳遞均需要通過主內(nèi)存(詳見:Java內(nèi)存模型)來完成。

  2.禁止指令重排序優(yōu)化。有volatile修飾的變量,賦值后多執(zhí)行了一個“l(fā)oad addl $0x0, (%esp)”操作,這個操作相當(dāng)于一個內(nèi)存屏障(指令重排序時不能把后面的指令重排序到內(nèi)存屏障之前的位置),只有一個CPU訪問內(nèi)存時,并不需要內(nèi)存屏障;(什么是指令重排序:是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應(yīng)電路單元處理)。

volatile 性能:

  volatile 的讀性能消耗與普通變量幾乎相同,但是寫操作稍慢,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行。


synchronized:

synchronized是針對修飾代碼塊,對象方法,或者類對象(類對象就是修飾靜態(tài)方法或者對象)

java的內(nèi)置鎖:每個java對象都可以用做一個實(shí)現(xiàn)同步的鎖,這些鎖成為內(nèi)置鎖。線程進(jìn)入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得內(nèi)置鎖的唯一途徑就是進(jìn)入這個鎖的保護(hù)的同步代碼塊或方法。
java內(nèi)置鎖是一個互斥鎖,這就是意味著最多只有一個線程能夠獲得該鎖,當(dāng)線程A嘗試去獲得線程B持有的內(nèi)置鎖時,線程A必須等待或者阻塞,直到線程B釋放這個鎖,如果B線程不釋放這個鎖,那么A線程將永遠(yuǎn)等待下去。
java的對象鎖和類鎖:java的對象鎖和類鎖在鎖的概念上基本上和內(nèi)置鎖是一致的,但是,兩個鎖實(shí)際是有很大的區(qū)別的,對象鎖是用于對象實(shí)例方法,或者一個對象實(shí)例上的,類鎖是用于類的靜態(tài)方法或者一個類的class對象上的。我們知道,類的對象實(shí)例可以有很多個,但是每個類只有一個class對象,所以不同對象實(shí)例的對象鎖是互不干擾的,但是每個類只有一個類鎖。但是有一點(diǎn)必須注意的是,其實(shí)類鎖只是一個概念上的東西,并不是真實(shí)存在的,它只是用來幫助我們理解鎖定實(shí)例方法和靜態(tài)方法的區(qū)別的。

這里涉及到內(nèi)置鎖的一個概念(此概念出自java并發(fā)編程實(shí)戰(zhàn)第二章):對象的內(nèi)置鎖和對象的狀態(tài)之間是沒有內(nèi)在的關(guān)聯(lián)的,雖然大多數(shù)類都將內(nèi)置鎖用做一種有效的加鎖機(jī)制,但對象的域并不一定通過內(nèi)置鎖來保護(hù)。當(dāng)獲取到與對象關(guān)聯(lián)的內(nèi)置鎖時,并不能阻止其他線程訪問該對象,當(dāng)某個線程獲得對象的鎖之后,只能阻止其他線程獲得同一個鎖。之所以每個對象都有一個內(nèi)置鎖,是為了免去顯式地創(chuàng)建鎖對象。
類鎖和對象鎖是兩個不一樣的鎖,控制著不同的區(qū)域,它們是互不干擾的。同樣,線程獲得對象鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。
?
到這里,對synchronized的用法已經(jīng)有了一定的了解。這時有一個疑問,既然有了synchronized修飾方法的同步方式,為什么還需要synchronized修飾同步代碼塊的方式呢?而這個問題也是synchronized的缺陷所在。
synchronized的缺陷:當(dāng)某個線程進(jìn)入同步方法獲得對象鎖,那么其他線程訪問這里對象的同步方法時,必須等待或者阻塞,這對高并發(fā)的系統(tǒng)是致命的,這很容易導(dǎo)致系統(tǒng)的崩潰。如果某個線程在同步方法里面發(fā)生了死循環(huán),那么它就永遠(yuǎn)不會釋放這個對象鎖,那么其他線程就要永遠(yuǎn)的等待。這是一個致命的問題。
當(dāng)然同步方法和同步代碼塊都會有這樣的缺陷,只要用了synchronized關(guān)鍵字就會有這樣的風(fēng)險(xiǎn)和缺陷。既然避免不了這種缺陷,那么就應(yīng)該將風(fēng)險(xiǎn)降到最低。這也是同步代碼塊在某種情況下要優(yōu)于同步方法的方面。例如在某個類的方法里面:這個類里面聲明了一個對象實(shí)例,SynObject so=new SynObject();在某個方法里面調(diào)用了這個實(shí)例的方法so.testsy();但是調(diào)用這個方法需要進(jìn)行同步,不能同時有多個線程同時執(zhí)行調(diào)用這個方法。這時如果直接用synchronized修飾調(diào)用了so.testsy();代碼的方法,那么當(dāng)某個線程進(jìn)入了這個方法之后,這個對象其他同步方法都不能給其他線程訪問了。假如這個方法需要執(zhí)行的時間很長,那么其他線程會一直阻塞,影響到系統(tǒng)的性能。如果這時用synchronized來修飾代碼塊:synchronized(so){so.testsy();},那么這個方法加鎖的對象是so這個對象,跟執(zhí)行這行代碼的對象沒有關(guān)系,當(dāng)一個線程執(zhí)行這個方法時,這對其他同步方法時沒有影響的,因?yàn)樗麄兂钟械逆i都完全不一樣。

public class TestSynchronized { public void testone() { synchronized(this) { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } public synchronized void testtwo() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread thread1= new Thread( new Runnable() { public void run() { myt2.testone(); } }, "testone" ); Thread thread2= new Thread( new Runnable() { public void run() { myt2.testtwo(); } }, "testtwo" ); thread1.start();; thread2.start(); } }
不過這里還有一種特例,就是上面演示的例子,對象鎖synchronized同時修飾方法和代碼塊,這時也可以體現(xiàn)到同步代碼塊的優(yōu)越性,如果testone方法同步代碼塊后面有非常多沒有同步的代碼,而且有一個100000的循環(huán),這導(dǎo)致testone方法會執(zhí)行時間非常長,那么如果直接用synchronized修飾方法,那么在方法沒執(zhí)行完之前,其他線程是不可以訪問testtwo方法的,但是如果用了同步代碼塊,那么當(dāng)退出代碼塊時就已經(jīng)釋放了對象鎖,當(dāng)線程還在執(zhí)行test1的那個100000的循環(huán)時,其他線程就已經(jīng)可以訪問testtwo方法了。

這就讓阻塞的機(jī)會或者線程更少。讓系統(tǒng)的性能更優(yōu)越。
?
一個類的對象鎖和另一個類的對象鎖是沒有關(guān)聯(lián)的,當(dāng)一個線程獲得A類的對象鎖時,它同時也可以獲得B類的對象鎖。

總結(jié)

以上是生活随笔為你收集整理的volatile与synchronized 同步原理基础讲解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。