您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)Java中鎖機制synchronized和CAS有什么用的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
我們使用多線程肯定是為了提高效率,壓榨硬件的性能提高效率,假設(shè)多一個線程相當于多一個人干活,但是有時候人多了就不是很好管理,可能出現(xiàn)問題。
比如我現(xiàn)在搞一個多線程的demo,我的本意是每個線程都高呼“ZPNB!”,我寫下了如下的代碼。
public class ThreadDemo implements Runnable{ @Test public void testThread() { System.out.println("大聲告訴我:"); ThreadDemo demo = new ThreadDemo(); Thread threadOne = new Thread(demo,"張三:ZPNB"); Thread threadTwo = new Thread(demo,"李四:ZPNB"); Thread threadThree = new Thread(demo,"王二麻子:ZPNB"); Thread threadFour = new Thread(demo,"趙四:ZPNB"); threadOne.start(); threadTwo.start(); threadThree.start(); threadFour.start(); } @Override public void run() { // synchronized (this){ for( int i = 0; i < 10 ;i++ ){ try { System.out.println(Thread.currentThread().getName()); //這里設(shè)置0是因為Junit的限制你設(shè)置長了,他就執(zhí)行一段時間就不執(zhí)行了 Thread.sleep(0); } catch (InterruptedException e) { e.printStackTrace(); } } // } }
沒有加鎖的情況是這樣的,看起來很亂我希望他們每個人都喊十遍然后下一個人,顯然下面的結(jié)果不滿意
大聲告訴我: 李四:ZPNB 張三:ZPNB 李四:ZPNB 張三:ZPNB 李四:ZPNB 張三:ZPNB 張三:ZPNB 李四:ZPNB 張三:ZPNB 李四:ZPNB 張三:ZPNB 李四:ZPNB 張三:ZPNB 李四:ZPNB 張三:ZPNB 李四:ZPNB 張三:ZPNB 李四:ZPNB 張三:ZPNB 李四:ZPNB 王二麻子:ZPNB 趙四:ZPNB 王二麻子:ZPNB 趙四:ZPNB 王二麻子:ZPNB 趙四:ZPNB 趙四:ZPNB 趙四:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 趙四:ZPNB 王二麻子:ZPNB 趙四:ZPNB 王二麻子:ZPNB 趙四:ZPNB 王二麻子:ZPNB 趙四:ZPNB 王二麻子:ZPNB 趙四:ZPNB 王二麻子:ZPNB
但是如果我把synchronized的注釋取消就變成了我想要的依次每人喊十遍
大聲告訴我: 張三:ZPNB 張三:ZPNB 張三:ZPNB 張三:ZPNB 張三:ZPNB 張三:ZPNB 張三:ZPNB 張三:ZPNB 張三:ZPNB 張三:ZPNB 趙四:ZPNB 趙四:ZPNB 趙四:ZPNB 趙四:ZPNB 趙四:ZPNB 趙四:ZPNB 趙四:ZPNB 趙四:ZPNB 趙四:ZPNB 趙四:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 王二麻子:ZPNB 李四:ZPNB 李四:ZPNB 李四:ZPNB 李四:ZPNB 李四:ZPNB 李四:ZPNB 李四:ZPNB 李四:ZPNB 李四:ZPNB 李四:ZPNB
這就突出了鎖的重要性,我們希望有些線程能按照我們希望的一個順序依次來執(zhí)行,而不是先到先得的。
實際上每一個對象實際都擁有一個叫做監(jiān)視器monitor的東西,線程只有獲得了這個監(jiān)視器才能才能進入同步塊和同步方法,如果沒有獲取到監(jiān)視器的線程將會被阻塞在同步塊和同步方法的入口處,具體過程如下圖:
那如果沒獲取到監(jiān)視器怎么辦,有個同步隊列的東西,你沒得到監(jiān)視器就等一等,等上一個獲取監(jiān)視器的exit推出監(jiān)視器你再根據(jù)隊列順序去再獲取,當然可能在這個再獲取的過程碰到一個“新來的”沒進隊列直接跟你搶,你還沒搶過,那你就還要重復之前的等待過程。
其實這里還涉及一個鎖的“happen before”的概念(“ A hapen-bfore B,那么 A 的結(jié)果對 B 是可見的”),就是上一個線程如果對某些值有改寫,后一個應(yīng)該在這個基礎(chǔ)上改寫的原則,假設(shè)一個計算程序,值都改了,新的線程你還在拿原先的值再去計算是不對的,應(yīng)該是在新的值上面再去做操作,這樣多線程協(xié)作才有實際意義。
以下是關(guān)于synchronized作用范圍(基本是實際對象或者是類對象,如果你是類對象的話,那你new多少個實例對象還是被鎖的。)
CAS突然這個概念出來作為線程安全的一個實現(xiàn)方式出現(xiàn),那它和synchronized是一個什么樣的關(guān)系呢?
實際二者應(yīng)該是同級的概念,大家都是鎖,synchronized是悲觀鎖,基本就是來一個線程就是鎖起來,阻塞同步的。認為任何操作都有可能是沖突,所以按照最壞的情況來處理,線程競爭阻塞了就阻塞,阻塞結(jié)束了就喚醒阻塞的進程。
CAS就是compare and swap ,不是直接鎖起來,大概意思就是:
CAS(V,O,N),包含三個值分別為:V 內(nèi)存地址存放的實際值;O 預期的值(舊值);N 更新的新值。當V和O相同時,也就是說舊值和內(nèi)存中實際的值相同表明該值沒有被其他線程更改過,即該舊值O就是目前來說最新的值了,自然而然可以將新值N賦值給V。反之,V和O不相同,表明該值已經(jīng)被其他線程改過了則該舊值O不是最新版本的值了,所以不能將新值N賦給V,返回V即可。當多個線程使用CAS操作一個變量是,只有一個線程會成功,并成功更新,其余會失敗。失敗的線程會重新嘗試,當然也可以選擇掛起線程
CAS對于線程競爭沖突的情況相對來說就溫柔一些,他會有自己的重試機制,就是這次不行我等一會再去看看,而不是直接阻塞掛起再喚醒的狀態(tài),這樣太耗費時間了。
在Java.util,ConCurrent包里面很多都是用CAS來處理同步的問題,而不是直接來個synchronized來修飾。
實際上現(xiàn)在來看,還真不好說,因為在CAS的方案提出,實際上synchronized也是不斷的進步的。不能說CAS一定比synchronized好。
比如說CAS也會有自己的問題,最主要的有:ABA,自旋時間過長和只能保證一個共享變量的原子操作,雖然說都要相關(guān)的解決方案:
(1)ABA就是兩個線程第一個線程將最開始的A值改成B再改成A,第二個線程接手直接CAS,會得不到之前的轉(zhuǎn)換的過程,解決方式跟數(shù)據(jù)庫一樣加一個版本號1A 2B 3C解決。
(2)自旋時間過長就是線程競爭沖突,不停地重試,實際是一個循環(huán)操作,這個循環(huán)可能要等好長時間,導致所謂的自旋時間過長。
(3)只能操作一個共享原子,就讓這個原子變成一個對象,把要共享的都塞進去。
synchronized自身也在不斷地優(yōu)化自身,甚至也借鑒了CAS的思想在1.6里面。為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,在Java SE 1.6中,鎖一共有4種狀態(tài),級別從低到高依次是:無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖狀態(tài)和重量級鎖狀態(tài)。
偏向鎖(通過線程ID來看對象頭和棧幀里面查找線程ID(記錄的線程ID就是偏向的線程ID),有就獲取沒有就嘗試CAS設(shè)置自己為偏向的線程)
具體如下:
當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。如果測試成功,表示線程已經(jīng)獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設(shè)置成1(表示當前是偏向鎖),如果沒有設(shè)置,則使用CAS競爭鎖;如果設(shè)置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。
(替換鎖的指針替換成就獲得鎖,替換不成就自旋循環(huán)去找機會替換)
具體如下:
線程在執(zhí)行同步塊之前,JVM會先在當前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
(monitor監(jiān)視器鎖的實現(xiàn),最重的一步,因為涉及到用戶態(tài)和系統(tǒng)態(tài)切換。)
重量級鎖是依賴對象內(nèi)部的monitor鎖來實現(xiàn)。當系統(tǒng)檢查到鎖是重量級鎖之后,會把等待想要獲得鎖的線程進行阻塞,被阻塞的線程不會消耗cup。但是阻塞或者喚醒一個線程時,都需要操作系統(tǒng)來幫忙,需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài),而轉(zhuǎn)換狀態(tài)是需要消耗很多時間。
這么看來synchronized并不是那么不堪,未必你用CAS實現(xiàn)的就一定在某些環(huán)境比synchronized這個“元老”強。
感謝各位的閱讀!關(guān)于“Java中鎖機制synchronized和CAS有什么用”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。