Java基礎(chǔ)之多線程篇(二)

Java中的鎖

從不同的角度看碘裕,Java中有許多類型的鎖,下面是它們的簡(jiǎn)單介紹胶背。

  • 從是否鎖住同步資源來看

  • 1.1樂觀鎖

樂觀鎖認(rèn)為所有拿到共享數(shù)據(jù)的線程都不會(huì)修改數(shù)據(jù)杭措,只會(huì)查看數(shù)據(jù),因此在獲取共享數(shù)據(jù)時(shí)不會(huì)加鎖伟墙,適合讀操作多的場(chǎng)景翘鸭,不加鎖的特點(diǎn)能夠使其讀操作的性能大幅提升。

Java中是通過使用無鎖編程來實(shí)現(xiàn)戳葵,最常采用的是CAS算法就乓,Java原子類中的遞增操作就通過CAS自旋實(shí)現(xiàn)的。

  • 1.1.1 CAS

CAS全稱 Compare And Swap拱烁。在不使用鎖的情況下實(shí)現(xiàn)多線程之間的變量同步生蚁。

涉及到三個(gè)操作數(shù):需要讀寫的內(nèi)存值 oldValue,進(jìn)行比較的值 oldValue2戏自,要寫入的新值 newValue邦投。

JDK通過CPU的cmpxchg指令,去比較寄存器中的 oldValue2 和 內(nèi)存中的值 oldValue擅笔。如果相等尼摹,就把要寫入的新值 newValue 存入內(nèi)存中。如果不相等剂娄,就將內(nèi)存值 oldValue 賦值給寄存器中的值 oldValue2蠢涝。然后通過Java代碼中的while循環(huán)再次調(diào)用cmpxchg指令進(jìn)行重試,直到設(shè)置成功為止阅懦。

  • 1.2 悲觀鎖

悲觀鎖認(rèn)為自己在使用數(shù)據(jù)的時(shí)候一定有別的線程來修改數(shù)據(jù)和二,因此在獲取數(shù)據(jù)的時(shí)候會(huì)先加鎖,確保數(shù)據(jù)不會(huì)被別的線程修改耳胎,適合寫操作多的場(chǎng)景惯吕,先加鎖可以保證寫操作時(shí)數(shù)據(jù)正確惕它。

  • 從同步資源的競(jìng)爭(zhēng)程度來看

  • 2.1 偏向鎖

當(dāng)一段同步代碼長(zhǎng)時(shí)間只被同一個(gè)線程訪問時(shí),說明該塊同步代碼競(jìng)爭(zhēng)程度非常低废登,沒必要頻繁的加鎖解鎖消耗系統(tǒng)資源淹魄,所以可以讓線程來訪問同步代碼自動(dòng)獲取鎖,這便是偏向鎖堡距。

只有存在其他線程競(jìng)爭(zhēng)偏向鎖的時(shí)候甲锡,持有偏向鎖的線程才會(huì)釋放掉偏向鎖;偏向鎖在JDK1.6之后是自動(dòng)開啟的羽戒,可以通過-XX:-UseBiasedLocking來進(jìn)行操作缤沦。

  • 2.2 輕量級(jí)鎖

當(dāng)偏向鎖被其他線程競(jìng)爭(zhēng)時(shí),便會(huì)升級(jí)為輕量級(jí)鎖易稠;另外缸废,競(jìng)爭(zhēng)偏向鎖未成功時(shí),該線程會(huì)進(jìn)行自旋操作而不是直接阻塞驶社,從而提高系統(tǒng)性能企量。

  • 2.3 重量級(jí)鎖

當(dāng)競(jìng)爭(zhēng)偏向鎖的線程自旋達(dá)到一定次數(shù),或者在它自旋的時(shí)候亡电,又來了一個(gè)競(jìng)爭(zhēng)鎖的線程梁钾,這時(shí),輕量級(jí)鎖會(huì)升級(jí)為重量級(jí)鎖逊抡;升級(jí)成重量級(jí)鎖之后姆泻,會(huì)將除了持有鎖之外的線程都阻塞(自旋的停止自旋)。

  • 2.4 各量級(jí)鎖的轉(zhuǎn)換規(guī)則

鎖升級(jí)的順序是偏向鎖-->輕量級(jí)鎖-->重量級(jí)鎖冒嫡,鎖只可以升級(jí)不可以降級(jí)拇勃。

  • 從申請(qǐng)鎖的順序來看

  • 3.1 公平鎖

公平鎖是指多個(gè)線程按照申請(qǐng)鎖的順序來獲取鎖,線程直接進(jìn)入隊(duì)列中排隊(duì)孝凌,隊(duì)列中的第一個(gè)線程才能獲得鎖方咆。公平鎖的優(yōu)點(diǎn)是等待鎖的線程不會(huì)餓死。缺點(diǎn)是整體吞吐效率相對(duì)非公平鎖要低蟀架,等待隊(duì)列中除第一個(gè)線程以外的所有線程都會(huì)阻塞瓣赂,CPU喚醒阻塞線程的開銷比非公平鎖大。

例如:你去食堂吃飯片拍,你看到有人排列煌集,你便自覺的去隊(duì)尾排列,打飯的阿姨在打完一個(gè)飯的時(shí)候會(huì)按照順序叫下一個(gè)同學(xué)來打飯捌省。

  • 3.2 非公平鎖

非公平鎖是多個(gè)線程加鎖時(shí)直接嘗試獲取鎖苫纤,獲取不到才會(huì)到等待隊(duì)列的隊(duì)尾等待。但如果此時(shí)鎖剛好可用,那么這個(gè)線程可以無需阻塞直接獲取到鎖卷拘,所以非公平鎖有可能出現(xiàn)后申請(qǐng)鎖的線程先獲取鎖的場(chǎng)景喊废。非公平鎖的優(yōu)點(diǎn)是可以減少喚起線程的開銷,整體的吞吐效率高栗弟,因?yàn)榫€程有幾率不阻塞直接獲得鎖污筷,CPU不必喚醒所有線程。缺點(diǎn)是處于等待隊(duì)列中的線程可能會(huì)餓死乍赫,或者等很久才會(huì)獲得鎖瓣蛀。

例如:你去食堂打飯,雖然已經(jīng)有人在排隊(duì)了耿焊,但是阿姨剛打完上一個(gè)同學(xué)的飯揪惦,還沒來得及叫下一個(gè)人遍搞,你可以直接去插隊(duì)打飯罗侯,阿姨就不會(huì)給隊(duì)首的同學(xué)打飯,而是先給你打飯溪猿。

  • 從鎖的共享程度來看

  • 4.1 共享鎖

共享鎖是指該鎖可被多個(gè)線程所持有钩杰。JDK中的ReentrantReadWriteLock的ReadLock是共享鎖。

  • 4.2 排他鎖

排他鎖诊县,是指該鎖一次只能被一個(gè)線程所持有讲弄。JDK中的synchronized和JUC中Lock的實(shí)現(xiàn)類就是互斥鎖。

  • 從鎖是否可以被一個(gè)線程重復(fù)獲取來看

  • 可重入鎖

可重入鎖依痊,是指在同一個(gè)線程在外層方法獲取鎖的時(shí)候避除,再進(jìn)入該線程的內(nèi)層方法會(huì)自動(dòng)獲取鎖(前提鎖對(duì)象得是同一個(gè)對(duì)象或者class),不會(huì)因?yàn)橹耙呀?jīng)獲取過還沒釋放而阻塞胸嘁。Java中ReentrantLock和synchronized都是可重入鎖瓶摆,可重入鎖的一個(gè)優(yōu)點(diǎn)是可一定程度避免死鎖。

示例代碼

/**
 * 重入鎖示例代碼
 */
public class ReentrantLockDemo {

    public synchronized void getLock1(){
        System.out.println("獲取對(duì)象鎖的method1");
        getLock2();
    }

    public synchronized void getLock2(){
        System.out.println("獲取對(duì)象鎖的method2");
    }

    public static void main(String[] args) {
        new ReentrantLockDemo().getLock1();
    }
}

在執(zhí)行g(shù)etLock1方法時(shí)性宏,已經(jīng)獲取了對(duì)象鎖群井,執(zhí)行g(shù)etLock2方法時(shí)并未釋放鎖;而getLock2方法也是需要獲取對(duì)象鎖的毫胜,方法卻可以正常執(zhí)行书斜,這便可以說明synchronized是可重入鎖。

  • 不可重入鎖

不可重入鎖則不會(huì)讓一個(gè)線程獲取兩次酵使,相對(duì)可重入鎖來說荐吉,更容易造成死鎖。

volatile關(guān)鍵字

volatile關(guān)鍵字可以保證順序性以及可見性口渔,無法保證原子性稍坯,所以單純的使用volatile關(guān)鍵字是無法實(shí)現(xiàn)多線程安全的,但是如果設(shè)計(jì)巧妙的話,也可以較為輕量的解決多線程安全問題瞧哟,典型就是以“雙重校驗(yàn)鎖”的方式實(shí)現(xiàn)單例模式混巧。

/**
 * 雙重校驗(yàn)鎖 實(shí)現(xiàn)單例模式
 */
public class SinglePattern {

    private static volatile SinglePattern singlePattern;

    private SinglePattern(){}

    public static SinglePattern getInstance(){
        if(singlePattern == null){
            synchronized(SinglePattern.class){
                if(singlePattern == null){
                    singlePattern = new SinglePattern();
                }
            }
        }
        return singlePattern;
    }
}

java內(nèi)存模型

java內(nèi)存模型(Java Memory Model,JMM)是java虛擬機(jī)規(guī)范定義的勤揩,用來屏蔽掉java程序在各種不同的硬件和操作系統(tǒng)對(duì)內(nèi)存的訪問的差異咧党,這樣就可以實(shí)現(xiàn)java程序在各種不同的平臺(tái)上都能達(dá)到內(nèi)存訪問的一致性,下面簡(jiǎn)單介紹下與java內(nèi)存模型相關(guān)的happen-before原則陨亡。

  • 單線程happen-before原則:在同一個(gè)線程中傍衡,書寫在前面的操作happen-before后面的操作。
  • 鎖的happen-before原則:同一個(gè)鎖的unlock操作happen-before此鎖的lock操作负蠕。
  • volatile的happen-before原則:對(duì)一個(gè)volatile變量的寫操作happen-before對(duì)此變量的任意操作
  • happen-before的傳遞性原則:如果A操作happen-before B操作蛙埂,B操作happen-before C操作,那么A操作happen-before C 操作
  • 線程啟動(dòng)的happen-before原則:同一個(gè)線程的start方法happen-before此線程的其他方法遮糖。
  • 線程中斷的happen-before原則:對(duì)縣城interrupt方法的調(diào)用happen-before被中斷線程的檢測(cè)到中斷發(fā)送的代碼绣的。
  • 線程終結(jié)的happen-before原則:線程中的所有操作都happen-before線程的終止檢測(cè)。
  • 對(duì)象創(chuàng)建的happen-before原則:對(duì)象的初始化完成先于他的finalize方法的調(diào)用欲账。

ThreadLocal

ThreadLocal屡江,顧名思義,它不是一個(gè)線程赛不,而是線程的一個(gè)本地化對(duì)象惩嘉。當(dāng)工作于多線程中的對(duì)象使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程分配一個(gè)獨(dú)立的變量副本踢故。所以每一個(gè)線程都可以獨(dú)立地改變自己的副本文黎,而不會(huì)影響其他線程所對(duì)應(yīng)的副本。從線程的角度看殿较,這個(gè)變量就像是線程的本地變量耸峭,這也是類名中“Local”所要表達(dá)的意思。

Java的鎖的理念是“時(shí)間換空間”斜脂,而ThreadLocal的理念是“用空間換時(shí)間”抓艳,下面寫個(gè)例子演示下TheadLocal的用法。

public class ThreadLocalDemo implements Runnable{

    //設(shè)置一個(gè)threadLocal變量存儲(chǔ)Integer
    private ThreadLocal<Integer> formatter = new ThreadLocal();

    @Override
    public void run() {
        System.out.println("Thread Name= "+Thread.currentThread().getId()+" 當(dāng)前取到的值為 "+formatter.get());

        formatter.set(new Integer(new Random().nextInt(100)));

        System.out.println("Thread Name= "+Thread.currentThread().getId()+" 設(shè)置后的值為 = "+formatter.get());


    }

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
        Thread thread1 = new Thread(threadLocalDemo);
        Thread thread2 = new Thread(threadLocalDemo);
        Thread thread3 = new Thread(threadLocalDemo);
        Thread thread4 = new Thread(threadLocalDemo);
        Thread thread5 = new Thread(threadLocalDemo);
        Thread thread6 = new Thread(threadLocalDemo);
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
        thread3.join();
        thread4.start();
        thread4.join();
        thread5.start();
        thread5.join();
        thread6.start();
    }
}

完整工程代碼鏈接:https://github.com/youzhihua/Java-training

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帚戳,一起剝皮案震驚了整個(gè)濱河市玷或,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌片任,老刑警劉巖偏友,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異对供,居然都是意外死亡位他,警方通過查閱死者的電腦和手機(jī)氛濒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹅髓,“玉大人舞竿,你說我怎么就攤上這事×耄” “怎么了骗奖?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)醒串。 經(jīng)常有香客問我执桌,道長(zhǎng),這世上最難降的妖魔是什么芜赌? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任仰挣,我火速辦了婚禮,結(jié)果婚禮上缠沈,老公的妹妹穿的比我還像新娘膘壶。我一直安慰自己,他們只是感情好博烂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布香椎。 她就那樣靜靜地躺著漱竖,像睡著了一般禽篱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上馍惹,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天躺率,我揣著相機(jī)與錄音,去河邊找鬼万矾。 笑死悼吱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的良狈。 我是一名探鬼主播后添,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼薪丁!你這毒婦竟也來了遇西?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤严嗜,失蹤者是張志新(化名)和其女友劉穎粱檀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漫玄,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茄蚯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渗常。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壮不,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出皱碘,到底是詐尸還是另有隱情忆畅,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布尸执,位于F島的核電站家凯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏如失。R本人自食惡果不足惜绊诲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望褪贵。 院中可真熱鬧掂之,春花似錦、人聲如沸脆丁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽槽卫。三九已至跟压,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歼培,已是汗流浹背震蒋。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躲庄,地道東北人查剖。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像噪窘,于是被迫代替她去往敵國(guó)和親笋庄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • layout: posttitle: 《Java并發(fā)編程的藝術(shù)》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,826評(píng)論 1 19
  • 1.寫出synchronized的使用方式 synchronized的三種應(yīng)用方式 synchronized關(guān)鍵字...
    wuyuan0127閱讀 303評(píng)論 0 1
  • 第2章 java并發(fā)機(jī)制的底層實(shí)現(xiàn)原理 Java中所使用的并發(fā)機(jī)制依賴于JVM的實(shí)現(xiàn)和CPU的指令倔监。 2.1 vo...
    kennethan閱讀 1,432評(píng)論 0 2
  • 現(xiàn)在是凌晨12:57分直砂,我剛從10點(diǎn)半睡了會(huì),這會(huì)正和我弟換完班丐枉。聽著我爸睡的正香的小呼嚕聲哆键,心里安心不少,聽...
    簡(jiǎn)丹追求閱讀 489評(píng)論 1 0
  • 郭相麟 莫名喜歡 在說不清楚道不明白中 你的名字 就像天上的星星 閃爍在心中 你的臉龐洋溢青春的陽光 你的眼神透明...
    郭相麟閱讀 152評(píng)論 0 0