多線程之鎖(五)

并發(fā)編程中最常出現(xiàn)的情形就是多個線程共享一個資源嗤堰,這些共享的資源很可能導致錯誤或者數(shù)據(jù)不一致的情形戴质,需要想辦法來解決這種問題。

臨界區(qū)(critical section):最多只能有一個線程執(zhí)行的代碼塊來訪問某些共享資源踢匣。

一般鎖能夠防止多個線程同時訪問某些資源告匠,但是有些鎖可以允許多個線程并發(fā)的讀取共享資源,比如讀寫鎖离唬。

兩個基本的鎖機制:

  • synchronized關(guān)鍵字后专,之前也提到過
  • Lock接口

鎖的注意事項

鎖是最常用的同步方法之一。但是在高并發(fā)情況下鎖的競爭會導致程序的性能下降输莺。為了降低這種副作用戚哎,這里有一些使用鎖的建議。

  • 減少鎖的持有時間嫂用。
  • 減小鎖的粒度型凳,即縮小鎖作用的對象范圍。
  • 鎖分離嘱函,如讀多寫少的場合甘畅,可以使用讀寫鎖。其余需要使用獨占鎖的時候,嘗試根據(jù)功能疏唾,分離鎖蓄氧。
  • 鎖粗化:這個和減少鎖的持有時間相反,根據(jù)具體場景來衡量槐脏,假如某個線程不斷的請求喉童,同步和釋放鎖,也會浪費性能顿天,根據(jù)實際情況進行權(quán)衡泄朴。

在學習或者使用Java的過程中進程會遇到各種各樣的鎖的概念:公平鎖、非公平鎖露氮、自旋鎖、可重入鎖钟沛、偏向鎖畔规、輕量級鎖、重量級鎖恨统、讀寫鎖叁扫、互斥鎖等等。

這篇文章整理了各種常見的Java鎖:http://www.importnew.com/19472.html

Lock與synchronized的區(qū)別

在Lock接口出現(xiàn)前畜埋,Java都是依靠synchronized關(guān)鍵字的莫绣,在JavaSE5之后,新增了Lock接口以及相關(guān)類來實現(xiàn)類似功能悠鞍。

  • Lock接口在使用時需要顯示的獲取和釋放鎖对室,synchronized關(guān)鍵字是隱式獲取和釋放鎖
  • Lock接口更加靈活,在獲取鎖的時候可以指定更多的操作咖祭,可中斷鎖掩宜,超時獲取鎖,指定時間釋放鎖么翰,非阻塞的獲取鎖牺汤。
  • Lock接口可以允許讀寫分離,多個讀浩嫌,但是只有一個寫

Lock可以說是synchronzed的增強版檐迟。

  • 超時獲取鎖:在指定時間未獲取到鎖,則返回
  • 非阻塞獲取鎖:當前線程嘗試獲取鎖码耐,如果未獲取到追迟,立刻返回,如果成功則獲取到鎖伐坏。
  • 能被中斷的獲取鎖:獲取到鎖的線程可以響應中斷怔匣。
Lock.png

重入鎖-ReenterantLock

可重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數(shù)獲得鎖之后 每瞒,內(nèi)層遞歸函數(shù)仍然有獲取該鎖的代碼金闽,但不受影響。

Java中重入鎖使用java.util.concurrent.locks.ReentrantLock實現(xiàn)剿骨。syncrhonzed也是可重入鎖代芜。

// 一個簡單的案例
public class ReentrantLockTest implements Runnable{
    private ReentrantLock lock = new ReentrantLock();

    public void get() {
        // get兩次加鎖,set又加鎖浓利。
        lock.lock();
        lock.lock();
        System.out.println("線程當前ID:" + Thread.currentThread().getId());
        set();
        lock.unlock();
        lock.unlock();
    }

    public void set() {
        lock.lock();
        System.out.println("線程當前ID:" + Thread.currentThread().getId());
        lock.unlock();
    }

    @Override
    public void run() {
        get();
    }

    public static void main(String[] args) {
        ReentrantLockTest test = new ReentrantLockTest();
        new Thread(test,"A").start();
        new Thread(test,"B").start();
        new Thread(test,"C").start();
    }
}

ReentrantLock有幾個重要方法:

  • lock() : 獲得鎖挤庇,如果鎖已經(jīng)被占用,則等待
  • lockInterruptibly() : 獲得鎖贷掖,但優(yōu)先響應中斷
  • tryLock() : 無阻塞鎖嫡秕,如果成功返回true,失敗返回false苹威,該方法不等的昆咽,立刻返回
  • tryLock(long time,TimeUnit unit) : 在給定時間內(nèi)嘗試獲得鎖
  • unlock(): 釋放鎖

這幾個方法都比較簡單,可以自行嘗試牙甫。

讀寫鎖-ReadWriteLock

讀寫分離鎖可以有效的減少鎖競爭掷酗,以提升系統(tǒng)性能。

非阻塞 阻塞
阻塞 阻塞
  • 讀讀不互斥
  • 讀寫互斥
  • 寫寫互斥

如果在系統(tǒng)中窟哺,讀操作的次數(shù)遠遠大于寫操作泻轰,則讀寫鎖就可以發(fā)揮最大的功效。JDK并發(fā)包中提供讀寫鎖的實現(xiàn)是ReentrantReadWriteLock且轨。

// 這個demo浮声,用來驗證讀寫鎖的性能比一般的鎖要好。
// 直接運行此demo殖告,程序幾秒鐘就可以運行完畢
// 如果注釋掉讀寫所阿蝶,讓sreadLock = sLock, sWriteLock = sLock。那么程序要運行20多秒才結(jié)束

public class ReadWriteLockDemo {
    private static Lock sLock = new ReentrantLock();
    private static ReadWriteLock sReadWriteLock = new ReentrantReadWriteLock();
    // 分別獲取讀寫鎖
    private static Lock sReadLock = sReadWriteLock.readLock();
//    private static Lock sReadLock = sLock;
    private static Lock sWriteLock = sReadWriteLock.writeLock();
//    private static Lock sWriteLock = sLock;

    private int value;

    // 模擬讀操作
    public int read() throws InterruptedException {
        try {
            sReadLock.lock();
            Thread.sleep(1000);
            return value;
        }finally {
            sReadLock.unlock();
        }
    }

    // 模擬寫操作
    public void write(int index) throws InterruptedException {
        try {
            sWriteLock.lock();
            Thread.sleep(1000);
            value = index;

        }finally {
            sWriteLock.unlock();
        }
    }

    static class ReadRunnable implements Runnable{
        private ReadWriteLockDemo mDemo;
        public ReadRunnable(ReadWriteLockDemo demo) {
            mDemo = demo;
        }
        @Override
        public void run() {
            try {
                mDemo.read();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class WriteRunnable implements Runnable{
        private ReadWriteLockDemo mDemo;
        public WriteRunnable(ReadWriteLockDemo demo) {
            mDemo = demo;
        }
        @Override
        public void run() {
            try {
                mDemo.write(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ReadWriteLockDemo demo = new ReadWriteLockDemo();
        ReadRunnable readRunnable = new ReadRunnable(demo);
        WriteRunnable writeRunnable = new WriteRunnable(demo);
        for (int i = 0; i < 20; i++) {
            if (i < 18){
                new Thread(readRunnable).start();
            }else {
                new Thread(writeRunnable).start();
            }
        }
    }
}

鎖的公平性

重入鎖和讀寫鎖黄绩,構(gòu)造器都提供了一個參數(shù)fair羡洁,允許你控制鎖的公平性,默認情況下是不公平的爽丹。
非公平鎖:在多個線程搶占鎖的時候筑煮,系統(tǒng)隨機挑選一個線程持有鎖。
公平鎖:多個線程搶占鎖的時候粤蝎,系統(tǒng)挑選等待時間最長的線程持有鎖真仲。

我們只需要在構(gòu)造鎖對象的時候,傳入true參數(shù)即可獲得公平鎖對象初澎。

new ReentrantLock(true);
new ReentrantReadWriteLock(true);

Condition

在JDK內(nèi)部秸应,重入鎖和Condition對象經(jīng)常一起用到虑凛。之前我們在并發(fā)基礎(chǔ)中提到過wait與notify要與synchronized關(guān)鍵字配合使用。Condition與重入鎖一起使用软啼,功能與wait和notify類似桑谍。

Lock接口的newCondition()方法可以生成一個與當前重入鎖綁定的Condition實例,利用Condition我們可以讓線程在特定的時間等待祸挪,在特定的時刻受到通知锣披。

以ArrayBlockingQueue為例:

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    //存儲數(shù)據(jù)元素
    final Object[] items;
    /** 主要的鎖 */
    final ReentrantLock lock;

    /** 等待taoke的條件 */
    private final Condition notEmpty;

    /** 等待put的條件 */
    private final Condition notFull;
    
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        //構(gòu)造鎖與condition
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }   
    
    
    
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                //如果隊列為空,等待隊列非空的信號
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
    
    // 如果入隊成功贿条,發(fā)出不空的信號
    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
                
        //發(fā)信號
        notEmpty.signal();
    }    

    
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
               //如果隊列已滿雹仿,等待隊列有足夠的空間
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }     
    
    //如果出隊成功,發(fā)出 不滿 的信號
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        //發(fā)信號    
        notFull.signal();
        return x;
    }
    
}

最后

這篇文章主要說了下使用鎖的時候需要注意的幾點整以,然后提了重入鎖與讀寫鎖胧辽。公平鎖,最后說了重入鎖的搭檔Condition公黑。
篇幅有限票顾,沒辦法面面俱到,感興趣的還望自己再摸索帆调。希望能幫助大家。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末豆同,一起剝皮案震驚了整個濱河市番刊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌影锈,老刑警劉巖芹务,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鸭廷,居然都是意外死亡枣抱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門辆床,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佳晶,“玉大人,你說我怎么就攤上這事讼载〗窝恚” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵咨堤,是天一觀的道長菇篡。 經(jīng)常有香客問我,道長一喘,這世上最難降的妖魔是什么驱还? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上议蟆,老公的妹妹穿的比我還像新娘闷沥。我一直安慰自己,他們只是感情好咪鲜,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布狐赡。 她就那樣靜靜地躺著,像睡著了一般疟丙。 火紅的嫁衣襯著肌膚如雪颖侄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天享郊,我揣著相機與錄音览祖,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的绷旗。 我是一名探鬼主播律胀,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼勒极!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤箕般,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后舔清,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丝里,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年体谒,在試婚紗的時候發(fā)現(xiàn)自己被綠了杯聚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡抒痒,死狀恐怖幌绍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情故响,我是刑警寧澤纷捞,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站被去,受9級特大地震影響主儡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惨缆,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一糜值、第九天 我趴在偏房一處隱蔽的房頂上張望丰捷。 院中可真熱鬧,春花似錦寂汇、人聲如沸病往。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽停巷。三九已至,卻和暖如春榕栏,著一層夾襖步出監(jiān)牢的瞬間畔勤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工扒磁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庆揪,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓妨托,卻偏偏與公主長得像缸榛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子兰伤,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

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

  • 1.解決信號量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 873評論 0 1
  • Java-Review-Note——4.多線程 標簽: JavaStudy PS:本來是分開三篇的内颗,后來想想還是整...
    coder_pig閱讀 1,639評論 2 17
  • 線程安全 當多個線程訪問一個對象時,如果不用考慮這些線程在運行時環(huán)境下的調(diào)度和交替執(zhí)行敦腔,也不需要進行額外的同步起暮,或...
    閩越布衣閱讀 759評論 0 6
  • nt!_TEB 線程環(huán)境塊(TEB結(jié)構(gòu))描述線程的狀態(tài) ole32!_TEB +0x000 NtTib : _NT...
    f675b1a02698閱讀 738評論 0 0
  • 讀書打卡第二周了,首先慶幸堅持了下來会烙,甚至這周開始讀的書多了,盡管沒有幾本真正讀透筒捺,至少進步了一個小小臺...
    左佐文珠閱讀 1,147評論 0 2