《JAVA并发编程实战》第十三章 显示锁

13.1  Lock与ReentrantLock

Reentrant 英[riːˈɛntrənt] 可重入; 可重入的; 重入; 可再入的; 重进入;

程序清单13-1 Lock接口定义
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;

public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}
public class ReentrantLock implements Lock, java.io.Serializable {...}

ReentrantLock提供了与synchronized相同的互斥性和可见性,可重入。
并且与synchronized相比,还为处理锁的不可用性问题提供了更高的灵活性。

为什么要创建一种与内置锁如此类似的新加锁机制?

大多数情况下内置锁都能很好地工作,但在功能上存在一些局限性。eg:

  1. 无法中断一个正在等待获取锁的线程
  2. 无法再请求获取一个锁时无限地等待下去
     (开始请求锁后,这个操作将无法取消,因此内置锁很难实现带有时间限制的操作)
  3. 内置锁无法实现非阻塞结构的加锁机制
程序清单13-2 使用ReentrantLock来保护对象状态
Lock lock = new ReentrantLock();

lock.lock();
try {
    //更新对象状态
    //捕获异常,并在必要时恢复不变性条件
} finally {
    lock.unlock();
}

13.1.1 轮询🔐与定时🔐

程序清单13-3 通过tryLock来避免锁顺序死锁(10.1.2)
public boolean transferMoney(Account fromAccount, Account toAccount, DollarAmount amount,long timeout, TimeUnit unit) {
    long fixedDelay = getFixedDelayComponentNanos(timeout,unit);
    long randMod = getRandomDelayComponentNanos(timeout,unit);
    long stopTime = System.nanoTime() + unit.toNanos(timeout);
    while(true) {
        if(fromAccount.lock.tryLock()) {
            try {
                if(toAccount.lock.tryLock()) {
                    try {
                        if(fromAccount.getBalance().compareTo(amount) < 0) {
                            throw new InsufficientResourcesException();
                        } else {
                            fromAccount.debit(amount);
                            toAccount.credit(amount);
                            return true;
                        }
                    } finally {
                        toAccount.lock.unlock();
                    }
                }
            } finally {
                fromAccount.lock.unlock();
            }
        }
        if(System.nanoTime() < stopTime)
            return false;
        NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
    }
    return false;
}
程序清单13-4 带有时间限制的加锁
//试图在Lock保护的共享通信线路上发送一条消息。
public boolean trySendOnSharedLine(String message,long timeout,TimeUnit unit) {
    long nanoToLock = unit.toNanos(timeout) - estimatedNanosToSend(message);
    if(!lock.tryLock(timeout, unit)) //如果不能在指定时间内完成,代码就会失败。独占加锁行为
        return false;
    try {
        return sendOnSharedLine(message); 
    } finally {
        lock.unlock();
    }
}

13.1.2 可中断的锁获取操作

程序清单13-5 可中断的锁获取操作
public boolean trySendOnSharedLine(String message) throws InterruptedException {
    lock.lockInterruptibly();
    try {
        return cancellableSendOnSharedLine(message); 
    } finally {
        lock.unlock();
    }
}

public boolean cancellableSendOnSharedLine(String message) throws InterruptedException{
    //TODO ....
    return true;
}

定时的tryLock()同样能响应中断,因此当需要实现一个定时的和可中断的锁操作时,可以使用tryLock方法

13.1.3 非块结构的加锁

13.2 性能考虑因数

Java 5.0中 ReentrantLock比内置锁有更好的竞争性能(是可伸缩性的关键要素。体现在锁的管理和调度)
从Java 6.0 开始 则内置锁synchronized 追平了ReentrantLock

13.3 公平性

ReentrantLock 构造函数提供2种选择:

1.非公平的锁(默认):允许【插队】:
  *1)当一个线程请求非公平锁时,如果在发出请求的同时改锁的状态变为可用,
2)只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中
那么这个线程将跳过队列中所有的等待线程并获得这个锁。

3)不提倡插队,但无法防止某个线程在合适的时候进行“插队”
2.公平的锁:线程按照发出请求的顺序来获得锁
新发出请求的线程将被安排在等待队列中,不会插队

为什么不希望所有的锁都是公平的?

  公平性将由于挂起和恢复线程时存在的开销而极大地降低性能。非公平锁的性能要高于公平锁的性能。

A线程持有锁 ——> B在排队等待 ——>A释放锁——>唤醒B——>B请求锁
A线程持有锁 ——> B在排队等待 ——>A释放锁,于此同时C请求锁,C可能在B被唤醒之前获得、使用、释放锁
——>唤醒B——>B请求锁(可能此时C已经执行完了)

什么情况下使用公平锁?

当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁

13.4 在synchronized和ReentrantLock之间选择

在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock:

  • 可定时的、可轮询的、可中断的锁获取操作
  • 公平队列
  • 非块结构的的锁

否则应该优先使用synchronized

13.5 读 - 写锁

一个资源可以被多个读操作访问, 或者被一个写操作访问,但两者不能同时进行。

程序清单13-6 ReadWriteLock接口
public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}
ReentrantReadWriteLock实现

ReentrantReadWriteLock的读写锁都可重入。
公平锁:等待时间最长的将获得锁。
    读线程持有锁,下一个是写入锁,此时所有的其它读线程都无法获取到锁,必须等待写线程执行完时间片后才行。
非公平锁:线程获取访问许可的顺序不定。
     写线程可以降级为读线程,读线程不可以升级为写线程(会产生死锁)

Listing 13.7. Wrapping a Map with a Readwrite Lock
public class ReadWriteMap<K,V> {
    private final Map<K,V> map;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock r = lock.readLock();
    private final Lock w = lock.writeLock();
    public ReadWriteMap(Map<K,V> map) {
        this.map = map;
}
    public V put(K key, V value) {
        w.lock();
        try {
            return map.put(key, value);
        } finally {
            w.unlock();
} }
    // Do the same for remove(), putAll(), clear()
    public V get(Object key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
} }
    // Do the same for other read-only Map methods
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容