国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

java并發(fā)編程專題(三)----詳解線程的同步

瀏覽:96日期:2022-08-30 09:19:09

有興趣的朋友可以回顧一下前兩篇

java并發(fā)編程專題(一)----線程基礎(chǔ)知識(shí)

java并發(fā)編程專題(二)----如何創(chuàng)建并運(yùn)行java線程

在現(xiàn)實(shí)開(kāi)發(fā)中,我們或多或少的都經(jīng)歷過(guò)這樣的情景:某一個(gè)變量被多個(gè)用戶并發(fā)式的訪問(wèn)并修改,如何保證該變量在并發(fā)過(guò)程中對(duì)每一個(gè)用戶的正確性呢?今天我們來(lái)聊聊線程同步的概念。

一般來(lái)說(shuō),程序并行化是為了獲得更高的執(zhí)行效率,但前提是,高效率不能以犧牲正確性為代價(jià)。如果程序并行化后, 連基本的執(zhí)行結(jié)果的正確性都無(wú)法保證, 那么并行程序本身也就沒(méi)有任何意義了。因此, 線程安全就是并行程序的根本和根基。解決這些問(wèn)題從臨界區(qū)的概念開(kāi)始。臨界區(qū)是訪問(wèn)一個(gè)共享資源在同一時(shí)間不能被超過(guò)一個(gè)線程執(zhí)行的代碼塊。

java為我們提供了同步機(jī)制,幫助程序員實(shí)現(xiàn)臨界區(qū)。當(dāng)一個(gè)線程想要訪問(wèn)一個(gè)臨界區(qū),它使用其中的一個(gè)同步機(jī)制來(lái)找出是否有任何其他線程執(zhí)行臨界區(qū)。如果沒(méi)有,這個(gè)線程就進(jìn)入臨界區(qū)。否則,這個(gè)線程通過(guò)同步機(jī)制暫停直到另一個(gè)線程執(zhí)行完臨界區(qū)。當(dāng)多個(gè)線程正在等待一個(gè)線程完成執(zhí)行的一個(gè)臨界 區(qū),JVM選擇其中一個(gè)線程執(zhí)行,其余的線程會(huì)等待直到輪到它們。臨界區(qū)有如下的規(guī)則:

如果有若干進(jìn)程要求進(jìn)入空閑的臨界區(qū),一次僅允許一個(gè)進(jìn)程進(jìn)入。 任何時(shí)候,處于臨界區(qū)內(nèi)的進(jìn)程不可多于一個(gè)。如已有進(jìn)程進(jìn)入自己的臨界區(qū),則其它所有試圖進(jìn)入臨界區(qū)的進(jìn)程必須等待。 進(jìn)入臨界區(qū)的進(jìn)程要在有限時(shí)間內(nèi)退出,以便其它進(jìn)程能及時(shí)進(jìn)入自己的臨界區(qū)。 如果進(jìn)程不能進(jìn)入自己的臨界區(qū),則應(yīng)讓出CPU,避免進(jìn)程出現(xiàn)“忙等”現(xiàn)象。

java語(yǔ)言為解決同步問(wèn)題幫我們提供了兩種機(jī)制來(lái)實(shí)現(xiàn):

1. synchronized關(guān)鍵字;2. Lock鎖及其實(shí)現(xiàn);

synchronized的作用

關(guān)鍵字synchronized 的作用是實(shí)現(xiàn)線程間的同步。它的工作是對(duì)同步的代碼加鎖,使得每一次, 只能有一個(gè)線程進(jìn)入同步塊,從而保證線程間的安全性。

關(guān)鍵宇synchronized 可以有多種用法。這里做一個(gè)簡(jiǎn)單的整理。

· 指定加鎖對(duì)象: 對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼前要獲得給定對(duì)象的鎖。· 直接作用于實(shí)例方法: 相當(dāng)于對(duì)當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖。. 直接作用于靜態(tài)方法: 相當(dāng)于對(duì)當(dāng)前類加鎖, 進(jìn)入同步代碼前要獲得當(dāng)前類的鎖。

1.給指定對(duì)象加鎖:

public class AccountingSync implements Runnable{ static AccountingSync instance=new AccountingSync() ; static int i =O; @Override public void run() ( for(int j=O; j<lOOOOOOO; j++) { synchronized (instance) { //對(duì)象鎖i++ ; } } } public static void main(String[] args) throws InterruptedException ( Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } /* public static void main(String[] args) throws InterruptedException ( Thread t1=new Thread(new AccountingSync()); Thread t2=new Thread(new AccountingSync()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } */

知道我為什么要給出兩個(gè)main方法讓大家參考嗎?上述鎖對(duì)象是鎖定AccountingSync實(shí)例對(duì)象。第一個(gè)main方法中t1 和 t2 兩個(gè)線程同時(shí)指向了instance實(shí)例,所以第7行的鎖對(duì)象synchronized (instance)在線程t1 和 線程 t2 獲得鎖的時(shí)候是獲取同一個(gè)對(duì)象的,這個(gè)時(shí)候的鎖是同一把鎖。但是在第二個(gè)main方法中我們可以看到線程t1 和 線程 t2分別對(duì)應(yīng)的是兩個(gè)不同的AccountingSync對(duì)象,這時(shí)候鎖對(duì)象獲得的是不同的AccountingSync實(shí)例,安全性是沒(méi)有保證的,大家可以動(dòng)手嘗試一下。

2.直接作用于實(shí)例方法:

public class TestSynchronized { public static void main(String[] args) { Tester2 a1 = new Tester2(); Th t1 = new Th(a1); t1.start(); Th t2 = new Th(a1); t2.start(); } } class Tester2 { public synchronized void say(String name) throws InterruptedException{ for(int i = 0;i<5;i++){Thread.sleep(1000);System.out.println();System.out.println(name +','+i+new Date().toLocaleString() ); } } } class Th extends Thread{ Tester2 test; public Th(Tester2 test1){ test = test1; } public void run(){ try {test.say(Thread.currentThread().getName()); } catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace(); } } }

對(duì)Tester2類中的方法使用synchronized很好理解,同一時(shí)刻如果t1正在調(diào)用say()方法,在他沒(méi)有執(zhí)行完畢并退出方法之前其余的線程是無(wú)法獲得該方法的。只能排隊(duì)等待知道t1執(zhí)行完畢。

3.作用于靜態(tài)方法:

public class Test1 { public static void main(String[] args) { for(int i=0;i<50;i++){Thread t1 = new Thread(new Sale(5));Thread t2 = new Thread(new Producted(5));t1.start();t2.start(); } } } class Shop{ static int a = 40; synchronized static void shopping(int b){ a -= b; System.out.println('售出 '+b+' 張大餅,'+'還剩 '+a+' 張大餅'); } synchronized static void factory(int c){ a += c; System.out.println('倉(cāng)庫(kù)還有 '+a+' 張大餅'); } } class Sale implements Runnable{ int b = 0; public Sale(int b){ this.b = b; } @Override public void run() { if(b<0){Thread.interrupted(); } Shop.shopping(b); try {Thread.sleep(1000);Shop.factory(b-5); } catch (InterruptedException e) {e.printStackTrace(); } } } class Producted implements Runnable{ int b = 0; public Producted(int b){ this.b = b; } @Override public void run() { Shop.factory(b); try {Thread.sleep(1000);Shop.shopping(b-5); } catch (InterruptedException e) {e.printStackTrace(); } } }

靜態(tài)方法前加synchronized這個(gè)鎖等價(jià)于鎖住了當(dāng)前類的class對(duì)象,因?yàn)殪o態(tài)方法或者是靜態(tài)關(guān)鍵字在本質(zhì)上是一個(gè)類對(duì)象,而不是成員對(duì)象,在內(nèi)存中位于方法區(qū)被所有的實(shí)例共享。即等同于synchronized(Shop.class)。我們需要注意的是鎖住了類并不代表鎖住了類所在的對(duì)象,類本身也是一種對(duì)象。它與類的實(shí)例是完全不同的兩個(gè)對(duì)象,在加鎖時(shí)不是相互依賴的,即對(duì)類加鎖并不與上面例子中的加鎖互斥,鎖住了子類或子類的對(duì)象與鎖住父類或父類的對(duì)象是不相關(guān)的。

synchronized的使用其實(shí)主要是前面兩種,對(duì)象鎖和方法鎖,靜態(tài)方法鎖我們并不常用到。其余的操作方式都是在這兩種的基礎(chǔ)上演變而來(lái),比如大家經(jīng)常說(shuō)的“塊級(jí)鎖”:

synchronized(object){ //代碼內(nèi)容 }

鎖住的其實(shí)并不是代碼塊,而是object這個(gè)對(duì)象,所以如果在其他的代碼中也發(fā)生synchronized(object)時(shí)就會(huì)發(fā)生互斥。我們?yōu)槭裁匆芯窟@些呢,因?yàn)槿绻覀儾恢牢覀冩i住的是什么,就不清楚鎖住了多大范圍的內(nèi)容,自然就不知道是否鎖住了想要得到互斥的效果,同時(shí)也不知道如何去優(yōu)化鎖的使用。

因此java中的synchronized就真正能做到臨界區(qū)的效果,在臨界區(qū)內(nèi)多個(gè)線程的操作絕對(duì)是串行的,這一點(diǎn)java絕對(duì)可以保證。同時(shí)synchronized造成的開(kāi)銷也是很大的,我們?nèi)绻麩o(wú)法掌握好他的粒度控制,就會(huì)導(dǎo)致頻繁的鎖征用,進(jìn)入悲觀鎖狀態(tài)。

volatile—-輕量級(jí)的synchronized

既然我們說(shuō)到了synchronized那就不得不提到volatile,在java中synchronized是控制并發(fā)的,我們知道在我們對(duì)一個(gè)變量執(zhí)行賦值操作的時(shí)候比如:i++,在執(zhí)行完畢之后i的結(jié)果其實(shí)是寫到緩存中的它并沒(méi)有及時(shí)的寫入到內(nèi)存,后續(xù)在某些情況下(比如cpu緩存不夠)再將cpu緩存寫入內(nèi)存,假設(shè)A線程正在執(zhí)行i++操作,而此時(shí)B線程也來(lái)執(zhí)行。B在執(zhí)行i++之前是不會(huì)自己跑到緩存中去取變量的值的,它只會(huì)去內(nèi)存中讀取i,很顯然i的值是沒(méi)有被更新的,為了防止這種情況出現(xiàn),volatile應(yīng)運(yùn)而生。

Java語(yǔ)言規(guī)范第三版中對(duì)volatile的定義如下: java編程語(yǔ)言允許線程訪問(wèn)共享變量,為了確保共享變量能被準(zhǔn)確和一致的更新,線程應(yīng)該確保通過(guò)排他鎖單獨(dú)獲得這個(gè)變量。Java語(yǔ)言提供了volatile,在某些情況下比鎖更加方便。如果一個(gè)字段被聲明成volatile,java線程內(nèi)存模型確保所有線程看到這個(gè)變量的值是一致的。

我們來(lái)看一個(gè)例子:

public class TestWithoutVolatile { private static boolean bChanged; public static void main(String[] args) throws InterruptedException { new Thread() { @Override public void run() { for (;;) { if (bChanged == !bChanged) { System.out.println('!='); System.exit(0); } } } }.start(); Thread.sleep(1); new Thread() { @Override public void run() { for (;;) { bChanged = !bChanged; } } }.start(); } }

在上例中我們?nèi)绻啻芜\(yùn)行會(huì)出現(xiàn)兩種結(jié)果,一種是正常打印:”!=”,還有一種就是程序會(huì)陷入死循環(huán)。但是我們?nèi)绻obChanged前面加上volatile的話則每次都會(huì)打印出”!=”,請(qǐng)讀者朋友們下去可以嘗試。在此處沒(méi)有加volatile之前之所以會(huì)出現(xiàn)有時(shí)可以出現(xiàn)正確結(jié)果有時(shí)則卡死的原因就在于兩個(gè)線程同時(shí)在運(yùn)行的過(guò)程中雙方都在操作bChanged變量,但是該變量的值對(duì)于同時(shí)在使用它的另一個(gè)線程來(lái)說(shuō)并不總是可見(jiàn)的,運(yùn)氣好的時(shí)候線程修改完值之后就寫入主存,運(yùn)氣不好的時(shí)候線程只在緩存中更新了值并未寫入主存。但是在加了volatile修飾之后效果則不同,因?yàn)関olatile可以保證變量的可見(jiàn)性。說(shuō)到可見(jiàn)性,我們來(lái)看一幅圖:

java并發(fā)編程專題(三)----詳解線程的同步

每一個(gè)線程都有相應(yīng)的工作內(nèi)存,工作內(nèi)存中有一份主內(nèi)存變量的副本,線程對(duì)變量的操作都在工作內(nèi)存中進(jìn)行(避免再次訪問(wèn)主內(nèi)存,提高性能),不同線程不能訪問(wèn)彼此的工作內(nèi)存,而通過(guò)將操作后的值刷新到主內(nèi)存來(lái)進(jìn)行彼此的交互,這就會(huì)帶來(lái)一個(gè)變量值對(duì)其他線程的可見(jiàn)性問(wèn)題。當(dāng)一個(gè)任務(wù)在工作內(nèi)存中變量值進(jìn)行改變,其他任務(wù)對(duì)此是不可見(jiàn)的,導(dǎo)致每一個(gè)線程都有一份不同的變量副本。而volatile恰恰可以解決這個(gè)可見(jiàn)性的問(wèn)題,當(dāng)變量被volatile修飾,如private volatile int stateFlag = 0; 它將直接通過(guò)主內(nèi)存中被讀取或者寫入,線程從主內(nèi)存中加載的值將是最新的。

但是volatile的使用有著嚴(yán)格的限制,當(dāng)對(duì)變量的操作依賴于以前值(如i++),或者其值被其他字段的值約束,這個(gè)時(shí)候volatile是無(wú)法實(shí)現(xiàn)線程安全的。被volatile修飾的變量必須獨(dú)立于程序的其他狀態(tài)。因?yàn)関olatile只是保證了變量的可見(jiàn)性,并不能保證操作的原子性,所謂原子性,即有“不可分”的意思,如對(duì)基本數(shù)據(jù)類型(java中排除long和double)的賦值操作a=6,如返回操作return a,這些操作都不會(huì)被線程調(diào)度器中斷,同一時(shí)刻只有一個(gè)線程對(duì)它進(jìn)行操作。看以下代碼:

public class Counter { public volatile static int count = 0; public static void inc() { //這里延遲1毫秒,使得結(jié)果明顯 try {Thread.sleep(1); } catch (InterruptedException e) { } count++; } public static void main(String[] args) { //同時(shí)啟動(dòng)1000個(gè)線程,去進(jìn)行i++計(jì)算,看看實(shí)際結(jié)果 for (int i = 0; i < 1000; i++) {new Thread(new Runnable() { @Override public void run() { Counter.inc(); }}).start(); } //這里每次運(yùn)行的值都有可能不同,可能為1000 System.out.println('運(yùn)行結(jié)果:Counter.count=' + Counter.count); } }

運(yùn)行上面的例子我們可以發(fā)現(xiàn)每次運(yùn)行的結(jié)果都不一樣,預(yù)期結(jié)果應(yīng)該是1000,盡管counter被volatile修飾,保證了可見(jiàn)性,但是counter++并不是一個(gè)原子性操作,它被拆分為讀取和寫入兩部分操作,我們需要用synchronized修飾:

publicstaticsynchronizedvoid incNum() { counter++; }

此時(shí)每次運(yùn)行結(jié)果都是1000,實(shí)現(xiàn)了線程安全。synchronized是一種獨(dú)占鎖,它對(duì)一段操作或內(nèi)存進(jìn)行加鎖,當(dāng)線程要操作被synchronized修飾的內(nèi)存或操作時(shí),必須首先獲得鎖才能進(jìn)行后續(xù)操作;但是在同一時(shí)刻只能有一個(gè)線程獲得相同的一把鎖,所以它只允許一個(gè)線程進(jìn)行操作。synchronized同樣能夠?qū)⒆兞孔钚轮邓⑿碌街鲀?nèi)存,當(dāng)一個(gè)變量只被synchronized方法操作時(shí),是沒(méi)有必要用volatile修飾的,所以我們接著把變量聲明修改為:

private static int counter;

多次運(yùn)行結(jié)果依舊是1000。

說(shuō)明:

上例中如果你按照上面這樣改完之后其實(shí)結(jié)果并是不1000,我多次運(yùn)行的結(jié)果都是先打印出”運(yùn)行結(jié)果:Counter.count=0”,然后線程卡死。究其原因,我猜可能是第一個(gè)線程等待一秒再執(zhí)行count++,然后后面的線程在這個(gè)等待過(guò)程中等不及的原因。java線程的運(yùn)行具有不確定性,不能保證線程會(huì)按部就班的順序執(zhí)行,所以會(huì)出現(xiàn)什么樣的后果很難預(yù)測(cè)。正確結(jié)果代碼如下:

public class Counter { public static int count = 0; public synchronized static void inc() { count++; } public static void main(String[] args) { //同時(shí)啟動(dòng)1000個(gè)線程,去進(jìn)行i++計(jì)算,看看實(shí)際結(jié)果 for (int i = 0; i < 1000; i++) {new Thread(new Runnable() { @Override public void run() { Counter.inc(); }}).start(); } //這里每次運(yùn)行的值都有可能不同,可能為1000 System.out.println('運(yùn)行結(jié)果:Counter.count=' + Counter.count); } }

綜上所述,由于volatile只能保證變量對(duì)多個(gè)線程的可見(jiàn)性,但不能保證原子性,它的同步機(jī)制是比較脆弱的,它在使用過(guò)程中有著諸多限制,對(duì)使用者也有更高的要求,相對(duì)而言,synchronized鎖機(jī)制是比較安全的同步機(jī)制,有時(shí)候出于提高性能的考慮,可以利用volatile對(duì)synchronized進(jìn)行代替和優(yōu)化,但前提是你必須充分理解其使用場(chǎng)景和涵義。

下一節(jié)我們接著分析Lock鎖。

以上就是java并發(fā)編程專題(三)----詳解線程的同步的詳細(xì)內(nèi)容,更多關(guān)于JAVA 線程同步的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 成人av手机在线观看 | 国产日韩视频在线观看 | 欧美国产在线观看 | 天天看片天天爽 | 女初高中福利视频在线观看 | 精品欧美一区二区三区四区 | 在线观看中文字幕国产 | 王朝影院一区二区三区入口 | 欧美韩国日本一区 | 亚洲欧美高清视频 | 精品国产九九 | 亚洲免费视频在线观看 | 日本三级在线观看中文字 | 91理论片午午伦夜理片久久 | 国内精品久久久久久久久蜜桃 | 午夜美女影院 | 成人精品视频在线观看 | 欧美激情亚洲一区中文字幕 | 99视频精品在线 | 成人a毛片久久免费播放 | 四川一级片 | 国产精品日本 | 一级aaaaaa毛片免费同男同女 | 亚洲精品国产一区二区三 | 99国产精品久久久久久久成人热 | 国产一有一级毛片视频 | 99精品在线 | 亚洲欧美日韩久久精品第一区 | 国产精品国内免费一区二区三区 | 日韩一区二区在线观看 | 全部免费a级毛片 | 一级毛片免费观看视频 | 亚洲欧美一区二区三区在线播放 | 亚洲精品久久九九精品 | 欧美一线高本道高清在线 | 亚洲国产成人久久一区www | 日韩一区二区久久久久久 | 成人毛片18女人毛片免费 | 久久在线视频免费观看 | 亚洲特黄视频 | 国产精品久久久久久久人热 |