带你进入java.util.concurrent.Locks

1.概览

简单地说,比起标准的同步块来说,lock是一个更加灵活、更加精密的线程同步机制。Lock是从java1.5开始推出的,它定义在java.util.concurrent.Lock包里,它提供了更广泛的锁操作。在这篇文章中,我们将探讨一些Lock接口的不同实现以及他们的应用。


2. Lock锁和同步块的区别

同步块和Lock API的些许区别:

1.同步块只能包含在一个方法内----而lock()和unlock()操作却可以在跨越多个不同的方法使用。

 2.同步块不支持公平性,任一个线程都能获取已经被释放的锁,不能指定优先权。但我们却可以使用Lock API指定公平属性从而实现公平性。它能确保等待时间最长的线程优先获取锁。

  3.当一个线程不能访问同步块时,它会被阻塞住。而 Lock API提供的有 tryLock()方法,使用该方法,只有在锁不被其他线程持有且可用时,才会真正获取锁。这将极大地降低阻塞时间。

  4.那些获取访问同步块的等待线程不能被中断,Lock API提供了一个 lockInterruptbly()方法,当线程正在等待锁时,该方法可以用于中断该线程。


3.Lock API

我们来看一下Lock接口中的方法:

    .void  lock()  -  如果锁可用就获取锁。如果锁不可用就阻塞住,直到锁被释放。

    .void  lockInterruptibly()  - 这个方法和lock()方法很类似,但是它允许阻塞的线程被中断,并且通过抛出一个java.lang.InterruptedException可以重新运行。

    .boolean tryLock()  -  这是lock()方法的非阻塞版本;它会立即试图获取锁,如果锁定成功的话,就返回true。

    .boolean tryLock(long timeout,TimeUnit timeUnit)  -这和tryLock()方法很像,只不过该方法,会在放弃获取锁之前,等待一段指定时间。

    .void unlock()  -解锁该锁实例。

一个锁实例应该永远处于解锁状态,这样才能避免死锁条件。使用lock的推荐方式是:代码块中应该包含try/catch 以及finally 块。

Lock lock = ...;

lock.lock();

try{

    // access to the shared resource

} finally{

    lock.unlock();

}


除了Lock接口之外,我们还有一个读写锁ReadWriteLock接口,此接口包含了一对锁,一个用于只读操作,一个用于写操作。只要没有写操作,读锁可以同时被多个线程持有。

ReadWriteLock 所声明的用于获取读、写锁的方法:

.Lock  readLock()  - 返回用于读操作的锁。

.Lock  writeLock()  - 返回用于写操作的锁。



4.Lock实现


 4.1 ReentrantLock

   ReentrantLock类实现了Lock接口。它除了提供和synchronized代码块一样的并发和内存语义还拥有可扩展的能力。

   我们来看一下,如何使用ReentrantLock做同步:

  public class SharedObject {

    //...

    ReentrantLock lock = new ReentrantLock();

    int counter = 0;

    public void perform() {

        lock.lock();

        try {

            // Critical section here

            count++;

        } finally {

            lock.unlock();

        }

    }

    //...

}

我们需要确保在try-finally块中包装了lock()和unlock()方法,只有这样才能避免死锁。

我们来看一下,tryLock() 是如何工作的:

public void performTryLock(){

    //...

    boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);

    if(isLockAcquired) {

        try {

            //Critical section here

        } finally {

            lock.unlock();

        }

    }

    //...

}

在这个案例中,调用tryLock()的线程将等待1秒钟,如果锁不可用则放弃等待。


4.2 ReentrantReadWriteLock

ReentrantReadWriteLock类实现了ReadWriteLock接口。

我们来看一下,一个线程获取ReadLock或WriteLock的规则:

   .Read Lock  - 如果没有线程获取了写锁或请求写锁,那么可以允许多个线程获取该读锁。

   . Write Lock  - 如果没有线程正在读或正在写,那么只有一个线程可以获取该写锁。


我们来看一下,如何使用ReadWriteLock:

public class SynchronizedHashMapWithReadWriteLock { Map syncHashMap = new HashMap<>();

    ReadWriteLock lock = new ReentrantReadWriteLock();

    //...

    Lock writeLock = lock.writeLock();

    public void put(String key, String value) {

        try {

            writeLock.lock();

            syncHashMap.put(key, value);

        } finally {

            writeLock.unlock();

        }

    }

    ...

    public String remove(String key){

        try {

            writeLock.lock();

            return syncHashMap.remove(key);

        } finally {

            writeLock.unlock();

        }

    }

    //...

}

对于这两个写方法,我们都需要用write lock来包围临界区,只有一个线程可以访问它。

Lock readLock = lock.readLock();

//...

public String get(String key){

    try {

        readLock.lock();

        return syncHashMap.get(key);

    } finally {

        readLock.unlock();

    }

}

public boolean containsKey(String key) {

    try {

        readLock.lock();

        return syncHashMap.containsKey(key);

    } finally {

        readLock.unlock();

    }

}

对于上面的俩个读方法,我们需要用读锁read lock 来包围临界区。在没有写操作的情况下,多个线程可以同时访问临界区。


4.3 StampedLock

StampedLock是在java8中引进的。它也同时支持读锁和写锁。然而 获取锁的方法会返回一个标记,这个标记stamp可以用于释放锁或检查该锁是否依然有效。

public class StampedLockDemo { Map map = new HashMap<>();

    private StampedLock lock = new StampedLock();

    public void put(String key, String value){

        long stamp = lock.writeLock();

        try {

            map.put(key, value);

        } finally {

            lock.unlockWrite(stamp);

        }

    }

    public String get(String key) throws InterruptedException {

        long stamp = lock.readLock();

        try {

            return map.get(key);

        } finally {

            lock.unlockRead(stamp);

        }

    }

}

StampedLock提供的另一个特性是:乐观锁。 大多数时候,读操作不需要等待写操作完成。正是基于此,我们并不需要一个完全的读锁(the full fledged read lock is not required)。作为替代,我们可以使用读锁。

public String readWithOptimisticLock(String key) {

    long stamp = lock.tryOptimisticRead();

    String value = map.get(key);

    if(!lock.validate(stamp)) {

        stamp = lock.readLock();

        try {

            return map.get(key);

        } finally {

            lock.unlock(stamp);             

        }

    }

    return value;

}



5. 结合Conditions一起使用

Condition 类为线程提供了一种条件执行能力,即线程会等待某些条件发生时,才执行临界区。

一个线程获取了对临界区的访问,但还没有执行相应动作的必要条件。例如,一个线程获取了对共享队列的访问,但该队列中没有任何可供消费的数据。

传统地,java提供的有wait()、notify()、以及notifyAll()方法用于线程间通信,Condition也有相似的机制,但是除此之外,我们可以指定多个条件Conditions:

public class ReentrantLockWithCondition { Stack stack = new Stack<>();

    int CAPACITY = 5;

    ReentrantLock lock = new ReentrantLock();

    Condition stackEmptyCondition = lock.newCondition();

    Condition stackFullCondition = lock.newCondition();

    public void pushToStack(String item){

        try {

            lock.lock();

            while(stack.size() == CAPACITY){

                stackFullCondition.await();

            }

            stack.push(item);

            stackEmptyCondition.signalAll();

        } finally {

            lock.unlock();

        }

    }

    public String popFromStack() {

        try {

            lock.lock();

            while(stack.size() == 0){

                stackEmptyCondition.await();

            }

            return stack.pop();

        } finally {

            stackFullCondition.signalAll();

            lock.unlock();

        }

    }

}


6.总结

在这篇文章中,我们已经看到了Lock接口的不同实现以及新引入的StampedLock类。我们也探索一下如何在多条件下使用Condition。

文章中的完整代码都在github上:sourceCode

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容