Java 并發(fā)編程(二)

一盅称、synchronized 關(guān)鍵字

Synchronized 是Java中的關(guān)鍵字、是一種同步鎖。其作用主要有:

  • 確保線程互斥的訪問(wèn)同步代碼
  • 保證共享變量的修改能夠及時(shí)可見(jiàn)
  • 有效解決指令重排序問(wèn)題

從語(yǔ)法上講缩膝,Synchronized有三種用法:

  • 修飾普通方法混狠,被修飾的方法稱(chēng)為同步方法,其作用的范圍是整個(gè)方法逞盆,作用的對(duì)象是調(diào)用這個(gè)方法的對(duì)象檀蹋;
  • 修飾靜態(tài)方法,其作用的范圍是整個(gè)靜態(tài)方法云芦,作用的對(duì)象是這個(gè)類(lèi)的所有對(duì)象俯逾;
  • 修飾代碼塊,被修飾的代碼塊稱(chēng)為同步語(yǔ)句塊舅逸,其作用的范圍是大括號(hào){}括起來(lái)的代碼桌肴,作用的對(duì)象是調(diào)用這個(gè)代碼塊的對(duì)象;
  • 修飾一個(gè)類(lèi)琉历,其作用的范圍是synchronized后面括號(hào)括起來(lái)的部分坠七,作用主的對(duì)象是這個(gè)類(lèi)的所有對(duì)象。

釋放鎖只會(huì)有兩種情況:

  • 獲取鎖的線程執(zhí)行完了該代碼塊旗笔,然后線程釋放對(duì)鎖的占有彪置;
  • 線程執(zhí)行發(fā)生異常,此時(shí)JVM會(huì)讓線程自動(dòng)釋放鎖蝇恶。

更多請(qǐng)參見(jiàn):[java中synchronized關(guān)鍵字的用法】(http://www.cnblogs.com/wl0000-03/p/5973039.html)

二拳魁、Lock

Lock和synchronied需要注意以下幾點(diǎn):

  • synchronized是Java語(yǔ)言的關(guān)鍵字,Lock是一個(gè)類(lèi)撮弧。
  • 采用synchronized不需要用戶去手動(dòng)釋放鎖潘懊,當(dāng)synchronized方法或者synchronized代碼塊執(zhí)行完之后,系統(tǒng)會(huì)自動(dòng)讓線程釋放對(duì)鎖的占用贿衍;而Lock則必須要用戶去手動(dòng)釋放鎖授舟,如果沒(méi)有主動(dòng)釋放鎖,就有可能導(dǎo)致出現(xiàn)死鎖現(xiàn)象贸辈。
  • 在資源競(jìng)爭(zhēng)不是很激烈的情況下释树,Synchronized的性能要優(yōu)于ReetrantLock,但是在資源競(jìng)爭(zhēng)很激烈的情況下裙椭,Synchronized的性能會(huì)下降幾十倍躏哩,
  • Lock可以讓等待鎖的線程響應(yīng)中斷,而synchronized卻不行揉燃,使用synchronized時(shí)扫尺,等待的線程會(huì)一直等待下去,不能夠響應(yīng)中斷炊汤;
  • 通過(guò)Lock可以知道有沒(méi)有成功獲取鎖正驻,而synchronized卻無(wú)法辦到弊攘。
  • Lock可以提高多個(gè)線程進(jìn)行讀操作的效率。

但是ReetrantLock的性能能維持常態(tài)姑曙;
Lock的源碼:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

首先lock()方法是平常使用得最多的一個(gè)方法襟交,就是用來(lái)獲取鎖。如果鎖已被其他線程獲取伤靠,則進(jìn)行等待捣域。由于在前面講到如果采用Lock,必須主動(dòng)去釋放鎖宴合,并且在發(fā)生異常時(shí)焕梅,不會(huì)自動(dòng)釋放鎖。因此一般來(lái)說(shuō)卦洽,使用Lock必須在try{}catch{}塊中進(jìn)行贞言,并且將釋放鎖的操作放在finally塊中進(jìn)行,以保證鎖一定被被釋放阀蒂,防止死鎖的發(fā)生该窗。通常使用Lock來(lái)進(jìn)行同步的話,是以下面這種形式去使用的:

Lock lock = ...;
lock.lock();
try{
    //處理任務(wù)
}catch(Exception ex){
     
}finally{
    lock.unlock();   //釋放鎖
}

tryLock()方法是有返回值的蚤霞,它表示用來(lái)嘗試獲取鎖酗失,如果獲取成功,則返回true昧绣,如果獲取失斆ど蕖(即鎖已被其他線程獲忍摺)舍肠,則返回false沛婴,也就說(shuō)這個(gè)方法無(wú)論如何都會(huì)立即返回抖苦。在拿不到鎖時(shí)不會(huì)一直在那等待堵漱。

Lock lock = ...;
lock.lock();
try{
    //處理任務(wù)
}catch(Exception ex){
     
}finally{
    lock.unlock();   //釋放鎖
}

lockInterruptibly()方法比較特殊夜牡,當(dāng)通過(guò)這個(gè)方法去獲取鎖時(shí)芳誓,如果線程正在等待獲取鎖醉锅,則這個(gè)線程能夠響應(yīng)中斷兔簇,即中斷線程的等待狀態(tài)。也就使說(shuō)硬耍,當(dāng)兩個(gè)線程同時(shí)通過(guò)lock.lockInterruptibly()想獲取某個(gè)鎖時(shí)垄琐,假若此時(shí)線程A獲取到了鎖,而線程B只有在等待经柴,那么對(duì)線程B調(diào)用threadB.interrupt()方法能夠中斷線程B的等待過(guò)程狸窘。

由于lockInterruptibly()的聲明中拋出了異常,所以lock.lockInterruptibly()必須放在try塊中或者在調(diào)用lockInterruptibly()的方法外聲明拋出InterruptedException坯认。

因此lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
}

Lock 的簡(jiǎn)單示例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {
    // 定義票
    private int tickets = 100;
    // 定義鎖對(duì)象
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 加鎖
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "張票");
                }
            } finally {
                // 釋放鎖
                lock.unlock();
            }
        }
    }
}

三翻擒、ReentrantLock

如果鎖具備可重入性氓涣,則稱(chēng)作為可重入鎖。像synchronized和ReentrantLock都是可重入鎖陋气,可重入性在我看來(lái)實(shí)際上表明了鎖的分配機(jī)制:基于線程的分配劳吠,而不是基于方法調(diào)用的分配。舉個(gè)簡(jiǎn)單的例子巩趁,當(dāng)一個(gè)線程執(zhí)行到某個(gè)synchronized方法時(shí)痒玩,比如說(shuō)method1,而在method1中會(huì)調(diào)用另外一個(gè)synchronized方法method2议慰,此時(shí)線程不必重新去申請(qǐng)鎖蠢古,而是可以直接執(zhí)行方法method2。

class MyClass {
    public synchronized void method1() {
        method2();
    }
     
    public synchronized void method2() {
    }
}

簡(jiǎn)單示例

  1. lock()的正確使用方法
public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意這個(gè)地方
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  
     
    public void insert(Thread thread) {
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了鎖");
            for(int i=0;i<5;i++) {
                arrayList.add(i);
            }
        } catch (Exception e) {
        }finally {
            System.out.println(thread.getName()+"釋放了鎖");
            lock.unlock();
        }
    }
}
  1. tryLock()的使用方法
public class Test {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意這個(gè)地方
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  
     
    public void insert(Thread thread) {
        if(lock.tryLock()) {
            try {
                System.out.println(thread.getName()+"得到了鎖");
                for(int i=0;i<5;i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }finally {
                System.out.println(thread.getName()+"釋放了鎖");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName()+"獲取鎖失敗");
        }
    }
}
  1. lockInterruptibly()響應(yīng)中斷的使用方法
public class Test {
    private Lock lock = new ReentrantLock();   
    public static void main(String[] args)  {
        Test test = new Test();
        MyThread thread1 = new MyThread(test);
        MyThread thread2 = new MyThread(test);
        thread1.start();
        thread2.start();
         
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }  
     
    public void insert(Thread thread) throws InterruptedException{
        lock.lockInterruptibly();   //注意褒脯,如果需要正確中斷等待鎖的線程便瑟,必須將獲取鎖放在外面,然后將InterruptedException拋出
        try {  
            System.out.println(thread.getName()+"得到了鎖");
            long startTime = System.currentTimeMillis();
            for(    ;     ;) {
                if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                    break;
                //插入數(shù)據(jù)
            }
        }
        finally {
            System.out.println(Thread.currentThread().getName()+"執(zhí)行finally");
            lock.unlock();
            System.out.println(thread.getName()+"釋放了鎖");
        }  
    }
}
 
class MyThread extends Thread {
    private Test test = null;
    public MyThread(Test test) {
        this.test = test;
    }
    @Override
    public void run() {
        try {
            test.insert(Thread.currentThread());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"被中斷");
        }
    }
}

四番川、ReadWriteLock

ReadWriteLock也是一個(gè)接口到涂,在它里面只定義了兩個(gè)方法:

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

五、ReentrantReadWriteLock

ReentrantReadWriteLock里面提供了很多豐富的方法颁督,不過(guò)最主要的有兩個(gè)方法:readLock()和writeLock()用來(lái)獲取讀鎖和寫(xiě)鎖践啄。

示例

public class Test {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
     
    public static void main(String[] args)  {
        final Test test = new Test();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.get(Thread.currentThread());
            };
        }.start();
         
    }  
     
    public void get(Thread thread) {
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();
             
            while(System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName()+"正在進(jìn)行讀操作");
            }
            System.out.println(thread.getName()+"讀操作完畢");
        } finally {
            rwl.readLock().unlock();
        }
    }
}

更多內(nèi)容

1. Java 并發(fā)編程:核心理論
2. 聊聊并發(fā)(一)深入分析Volatile的實(shí)現(xiàn)原理
3. Java中Synchronized的用法
4. Java并發(fā)編程:Lock
5. 死磕Java系列博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沉御,隨后出現(xiàn)的幾起案子屿讽,更是在濱河造成了極大的恐慌,老刑警劉巖吠裆,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伐谈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡试疙,警方通過(guò)查閱死者的電腦和手機(jī)诵棵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)祝旷,“玉大人履澳,你說(shuō)我怎么就攤上這事』初耍” “怎么了距贷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吻谋。 經(jīng)常有香客問(wèn)我忠蝗,道長(zhǎng),這世上最難降的妖魔是什么漓拾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任什湘,我火速辦了婚禮长赞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闽撤。我一直安慰自己得哆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布哟旗。 她就那樣靜靜地躺著贩据,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闸餐。 梳的紋絲不亂的頭發(fā)上饱亮,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音舍沙,去河邊找鬼近上。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拂铡,可吹牛的內(nèi)容都是我干的壹无。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼感帅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斗锭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起失球,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤岖是,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后实苞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體豺撑,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年黔牵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了前硫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荧止,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阶剑,到底是詐尸還是另有隱情跃巡,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布牧愁,位于F島的核電站素邪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猪半。R本人自食惡果不足惜兔朦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一偷线、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沽甥,春花似錦声邦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至恨诱,卻和暖如春媳瞪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背照宝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工蛇受, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厕鹃。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓兢仰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親熊响。 傳聞我的和親對(duì)象是個(gè)殘疾皇子旨别,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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