Java多線程7 Lock

Java多線程目錄

1 synchronized的缺陷

synchronized是java中的一個關鍵字,也就是說是Java語言內置的特性茧妒。那么為什么會出現(xiàn)Lock呢萧吠?

在上面一篇文章中,我們了解到如果一個代碼塊被synchronized修飾了桐筏,當一個線程獲取了對應的鎖纸型,并執(zhí)行該代碼塊時,其他線程便只能一直等待梅忌,等待獲取鎖的線程釋放鎖狰腌,而這里獲取鎖的線程釋放鎖只會有兩種情況:

1)獲取鎖的線程執(zhí)行完了該代碼塊,然后線程釋放對鎖的占有牧氮;

2)線程執(zhí)行發(fā)生異常琼腔,此時JVM會讓線程自動釋放鎖。

那么如果這個獲取鎖的線程由于要等待IO或者其他原因(比如調用sleep方法)被阻塞了踱葛,但是又沒有釋放鎖,其他線程便只能干巴巴地等待,試想一下洲赵,這多么影響程序執(zhí)行效率叠萍。

因此就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到抄腔。

再舉個例子:當有多個線程讀寫文件時绵患,讀操作和寫操作會發(fā)生沖突現(xiàn)象织狐,寫操作和寫操作會發(fā)生沖突現(xiàn)象旺嬉,但是讀操作和讀操作不會發(fā)生沖突現(xiàn)象邪媳。

但是采用synchronized關鍵字來實現(xiàn)同步的話废赞,就會導致一個問題:

如果多個線程都只是進行讀操作唉地,所以當一個線程在進行讀操作時,其他線程只能等待無法進行讀操作耕拷。

因此就需要一種機制來使得多個線程都只是進行讀操作時,線程之間不會發(fā)生沖突既峡,通過Lock就可以辦到。

另外传惠,通過Lock可以知道線程有沒有成功獲取到鎖。這個是synchronized無法辦到的盼砍。

總結一下浇坐,也就是說Lock提供了比synchronized更多的功能擒贸。但是要注意以下幾點:

1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性回右。Lock是一個類,通過這個類可以實現(xiàn)同步訪問;

2)Lock和synchronized有一點非常大的不同白华,采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執(zhí)行完之后虾攻,系統(tǒng)會自動讓線程釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖樊拓,就有可能導致出現(xiàn)死鎖現(xiàn)象。

2 Lock

2.1 ReentrantLock
public class LockTest {
    public static void main(String[] args) {

        new LockTest().init();
    }

    private void init() {
        final Outputer outputer = new Outputer();

        new Thread(new Runnable() {
            @Override
            public void run() {

                while (true) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("kpioneer");

                }
            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {

                while (true) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("Jack");

                }
            }
        }).start();
    }

    static class Outputer {
        
        Lock lock = new ReentrantLock();

        public void output(String name) {
            int len = name.length();
            lock.lock();
            /**
             *
             *不管程序運行是否異常都要釋放鎖
             * 否則再也進入不到改頁面
             */
            try {
                for (int i = 0; i < len; i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            } finally {
                lock.unlock();
            }
        }
    }
}
kpioneer
Jack
kpioneer
Jack
kpioneer
Jack
Jack
kpioneer
Jack
kpioneer
省略...
2.2 ReentrantReadWriteLock

實中有這樣一種場景:對共享資源有讀和寫的操作,且寫操作沒有讀操作那么頻繁。在沒有寫操作的時候摊鸡,多個線程同時讀一個資源沒有任何問題,所以應該允許多個線程同時讀取共享資源猎提;但是如果一個線程想去寫這些共享資源疙教,就不應該允許其他線程對該資源進行讀和寫的操作了。

針對這種場景,JAVA的并發(fā)包提供了讀寫鎖ReentrantReadWriteLock徊都,它表示兩個鎖,一個是讀操作相關的鎖几睛,稱為共享鎖;一個是寫相關的鎖夯接,稱為排他鎖晴弃,描述如下:

線程進入讀鎖的前提條件:

沒有其他線程的寫鎖肝匆,

沒有寫請求或者有寫請求,但調用線程和持有鎖的線程是同一個。

線程進入寫鎖的前提條件:

沒有其他線程的讀鎖

沒有其他線程的寫鎖

而讀寫鎖有以下三個重要的特性:

(1)公平選擇性:支持非公平(默認)和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平驹暑。

(2)重進入:讀鎖和寫鎖都支持線程重進入优俘。

(3)鎖降級:遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序漓滔,寫鎖能夠降級成為讀鎖透且。

public class ReadWriteLockTest {
    public static void main(String[] args) {

        final Queue q = new Queue();

        for (int i = 0; i < 3; i++) {

            new Thread() {
                @Override
                public void run() {

                    while (true) {
                        q.get();
                    }
                }
            }.start();

            new Thread() {
                @Override
                public void run() {
                    while (true) {
                        q.put(new Random().nextInt(10000));
                    }
                }
            }.start();
        }
    }
}

class Queue {

    //共享數(shù)據,只能有一個線程能寫該數(shù)據豁鲤,但可以有多個線程同時讀該數(shù)據秽誊。
    ReadWriteLock rwl = new ReentrantReadWriteLock();
    private Object data = null;//共享數(shù)據,只能有一個線程能寫數(shù)據琳骡,但可以有多個線程讀該數(shù)據

    public void get() {
        //上讀鎖锅论,其他線程只能讀不能寫
        rwl.readLock().lock();
        try {

            System.out.println(Thread.currentThread().getName() + " be ready to read data!");
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println(Thread.currentThread().getName() + " have read data :" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwl.readLock().unlock();
        }
    }

    public void put(Object data) {
        //上寫鎖,不允許其他線程讀也不允許寫
        rwl.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + " be ready to write data!");

            Thread.sleep((long) (Math.random() * 1000));

            this.data = data;
            System.out.println(Thread.currentThread().getName() + " have write data: " + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwl.writeLock().unlock();
        }
    }
}
Thread-0 be ready to read data!
Thread-2 be ready to read data!
Thread-2 have read data :null
Thread-0 have read data :null
Thread-1 be ready to write data!
Thread-1 have write data: 4664
Thread-1 be ready to write data!
Thread-1 have write data: 1849
Thread-3 be ready to write data!
Thread-3 have write data: 75
Thread-3 be ready to write data!
Thread-3 have write data: 8222
Thread-3 be ready to write data!
Thread-3 have write data: 3056
Thread-3 be ready to write data!
Thread-3 have write data: 6114
Thread-3 be ready to write data!
Thread-3 have write data: 6376
省略...

3 鎖的相關概念介紹

在前面介紹了Lock的基本使用楣号,這一節(jié)來介紹一下與鎖相關的幾個概念最易。

3.1 可重入鎖

如果鎖具備可重入性,則稱作為可重入鎖炫狱。像synchronized和ReentrantLock都是可重入鎖藻懒,可重入性在我看來實際上表明了鎖的分配機制:基于線程的分配,而不是基于方法調用的分配视译。舉個簡單的例子嬉荆,當一個線程執(zhí)行到某個synchronized方法時,比如說method1酷含,而在method1中會調用另外一個synchronized方法method2鄙早,此時線程不必重新去申請鎖,而是可以直接執(zhí)行方法method2第美。

看下面這段代碼就明白了:

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

上述代碼中的兩個方法method1和method2都用synchronized修飾了蝶锋,假如某一時刻,線程A執(zhí)行到了method1什往,此時線程A獲取了這個對象的鎖扳缕,而由于method2也是synchronized方法,假如synchronized不具備可重入性,此時線程A需要重新申請鎖躯舔。但是這就會造成一個問題驴剔,因為線程A已經持有了該對象的鎖,而又在申請獲取該對象的鎖粥庄,這樣就會線程A一直等待永遠不會獲取到的鎖丧失。

而由于synchronized和Lock都具備可重入性,所以不會發(fā)生上述現(xiàn)象惜互。

3.2 可中斷鎖

可中斷鎖:顧名思義布讹,就是可以相應中斷的鎖。
  在Java中训堆,synchronized就不是可中斷鎖描验,而Lock是可中斷鎖。
  如果某一線程A正在執(zhí)行鎖中的代碼坑鱼,另一線程B正在等待獲取該鎖膘流,可能由于等待時間過長,線程B不想等待了鲁沥,想先處理其他事情呼股,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖画恰。
  在前面演示lockInterruptibly()的用法時已經體現(xiàn)了Lock的可中斷性彭谁。

3.3 公平鎖

公平鎖即盡量以請求鎖的順序來獲取鎖。比如同是有多個線程在等待一個鎖允扇,當這個鎖被釋放時马靠,等待時間最久的線程(最先請求的線程)會獲得該所,這種就是公平鎖蔼两。

非公平鎖即無法保證鎖的獲取是按照請求鎖的順序進行的甩鳄。這樣就可能導致某個或者一些線程永遠獲取不到鎖。

在Java中额划,synchronized就是非公平鎖妙啃,它無法保證等待的線程獲取鎖的順序。

而對于ReentrantLock和ReentrantReadWriteLock俊戳,它默認情況下是非公平鎖揖赴,但是可以設置為公平鎖。

看一下這2個類的源代碼就清楚了:

   /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }
    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

另外在ReentrantLock類中定義了很多方法抑胎,比如:

isFair() //判斷鎖是否是公平鎖

isLocked() //判斷鎖是否被任何線程獲取了

isHeldByCurrentThread() //判斷鎖是否被當前線程獲取了

hasQueuedThreads() //判斷是否有線程在等待該鎖

在ReentrantReadWriteLock中也有類似的方法燥滑,同樣也可以設置為公平鎖和非公平鎖。不過要記住阿逃,ReentrantReadWriteLock并未實現(xiàn)Lock接口铭拧,它實現(xiàn)的是ReadWriteLock接口赃蛛。

3.4 讀寫鎖

讀寫鎖將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖搀菩。

正因為有了讀寫鎖呕臂,才使得多個線程之間的讀操作不會發(fā)生沖突。

ReadWriteLock就是讀寫鎖肪跋,它是一個接口歧蒋,ReentrantReadWriteLock實現(xiàn)了這個接口。

可以通過readLock()獲取讀鎖州既,通過writeLock()獲取寫鎖谜洽。

public class CacheDemo {
    private Map<String, Object> cache = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {


    }

    public Object getData(String key) {

        Object value = null;
        //首先開啟讀鎖,從緩存中去取
        readWriteLock.readLock().lock();
        try {
            value = cache.get(key);
            //如果緩存中沒有釋放讀鎖吴叶,上寫鎖
            if (value == null) {
                //對應queryDB()
                readWriteLock.readLock().unlock();
                //讀鎖必須unlock之后才能獲取寫鎖
                readWriteLock.writeLock().lock();
                try {
                    //對應queryDB()
                    value = queryDB();
                } finally {
                    //釋放寫鎖
                    readWriteLock.writeLock().unlock();
                }
                //然后再上讀鎖
                readWriteLock.readLock().lock();
            }
        } finally {
            //最后釋放讀鎖
            readWriteLock.readLock().unlock();
        }
        return value;

    }

    public Object queryDB() {
        return "aaaa";
    }
}
特別感謝:

ReentrantReadWriteLock讀寫鎖詳解
Lock和synchronized的區(qū)別和使用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末褥琐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子晤郑,更是在濱河造成了極大的恐慌,老刑警劉巖贸宏,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件造寝,死亡現(xiàn)場離奇詭異,居然都是意外死亡吭练,警方通過查閱死者的電腦和手機诫龙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲫咽,“玉大人签赃,你說我怎么就攤上這事》质” “怎么了锦聊?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長箩绍。 經常有香客問我孔庭,道長,這世上最難降的妖魔是什么材蛛? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任圆到,我火速辦了婚禮,結果婚禮上卑吭,老公的妹妹穿的比我還像新娘芽淡。我一直安慰自己,他們只是感情好豆赏,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布挣菲。 她就那樣靜靜地躺著富稻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪己单。 梳的紋絲不亂的頭發(fā)上唉窃,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音纹笼,去河邊找鬼纹份。 笑死,一個胖子當著我的面吹牛廷痘,可吹牛的內容都是我干的蔓涧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼笋额,長吁一口氣:“原來是場噩夢啊……” “哼元暴!你這毒婦竟也來了?” 一聲冷哼從身側響起兄猩,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤茉盏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后枢冤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸠姨,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年淹真,在試婚紗的時候發(fā)現(xiàn)自己被綠了讶迁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡核蘸,死狀恐怖巍糯,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情客扎,我是刑警寧澤祟峦,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站徙鱼,受9級特大地震影響搀愧,放射性物質發(fā)生泄漏。R本人自食惡果不足惜疆偿,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一咱筛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧杆故,春花似錦迅箩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至火脉,卻和暖如春奕塑,著一層夾襖步出監(jiān)牢的瞬間堂污,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工龄砰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盟猖,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓换棚,卻偏偏與公主長得像式镐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子固蚤,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容