并发编程实战--显式锁


ReentrantLock并不是一种替代内置加锁的方法,而是当加锁机制不适用时,作为一种可选择的高级功能。

1.Lock与ReentrantLock

ReentrantLock实现了Lock接口,ReentrantLock出了提供内存可见性和互斥性,还提供了可重入的加锁语义。
内置锁无法中断一个正在等待获取锁的线程,或者无法在请求获取一个锁时无限的等待下去。
内置锁必须在获取该锁的代码块中释放,并且与异常处理操作实现了很好的交互,但却无法实现非阻塞结构的加锁规则。
ReentrantLock必须在finally块中释放锁,否则很可能造成死锁。

1.1 轮询锁与定时锁

可定时与可轮询的锁获取模式是由tryLock方法实现的,与无条件的锁获取模式相比,具有更完善的错误恢复机制。
防止死锁的唯一方法就是构造程序时避免出现不一致的锁顺序。可定时与可轮询的锁提供了另一种选择:避免死锁的发生。

这种锁获取方式,会释放已经获得的锁,然后重新尝试获取所有锁。
定时的tryLock能够在带有时间限制的操作中实现独占加锁行为。

1.2 可中断的锁获取操作

当需要实现一个可定时的和可中断的锁获取操作,可以使用tryLock方法

    ReentrantLock lock = new ReentrantLock();
    public void sendOnSharedLine(String message) throws InterruptedException {
    	//获取锁,除非当前线程被中断
    	//如果锁没有被另一个线程持有,则获取锁并立即返回,将锁持有计数设置为1。
    	//如果当前线程已经持有锁,则计数器加1,立即返回
    	//如果锁被其他线程持有,则阻塞
        lock.lockInterruptibly();
        try {
            //do something
        } finally {
            lock.unlock();
        }
    }

1.3 非块结构的加锁

内置锁只能在代码块加锁和释放锁,不灵活。
内置锁可以通过降低锁的粒度来提高可伸缩性。
锁分段技术,可以使每个线程能独立的对链表的不同部分进行操作。每个节点的锁将保护链接指针以及在该节点中存储的数据,因此当遍历或修改链表时,我们必须持有该节点上的这个锁。

2.性能考虑因素

如果有越多得资源耗费在锁的管理和调度上,那么应用程序得到的资源就越少。锁的实现方式越好,将需要越少的系统调度和上下文切换。

性能是一个不断变化的指标,如果在昨天的测试基准中发现X比Y快,那么在今天就可能已经过时了。

3.公平性

    public ReentrantLock() {
        sync = new NonfairSync();
    }

ReentrantLock默认非公平锁:线程不在乎顺序,允许插队。只有当锁被某个线程持有时,新发出的请求才会被放入队列。
公平锁:线程按照他们发出请求的顺序来获得锁,新加入的请求将被放入队列,不管当前锁是否被释放。

非公平锁的性能一般高于公平锁:在恢复一个被挂起的线程与该线程真正开始运行之间存在严重的延迟。
当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,应该使用公平锁。

4.在synchronized和ReentrantLock之间进行选择

ReentrantLock的危险性比同步机制要高。
仅当内置锁不满足现有需求时,才考虑使用显式锁。

在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的,可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。否则,应该有限使用synchronized。

内置锁还可以在调用帧中给出获取了哪些锁,并能监测和识别发生死锁的线程。JVM并不知道那些线程持有ReentrantLock,需要通过一个管理和调试接口进行注册,从而将相关的加锁信息放到线程转储中并通过其他的管理接口和调试接口来访问。

5.读-写锁

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

在读取锁和写入锁之间的交互可以采用多种实现方式。ReadWriteLock中的一些可选实现包括:

  • 释放优先:当一个写入操作释放写入锁,并且队列中同时存在读线程和写线程,那么应该优先选择读线程,写线程,还是最先发出请求的线程?
  • 读线程插队:如果读线程持有锁,但有写线程正在等待,那么新到的读线程能否立即获得访问权,还是应该在写线程后面等待?如果允许读线程插队,那么虽然会提高并发性,但写线程会发生饥饿。
  • 重入性:读取锁和写入锁是否可重入?
  • 降级:如果一个线程持有写入锁,那么它能否在不释放该锁的情况下获得读取锁?这可能使写入锁被降级成读取锁,同时不允许同其他写线程修改被保护的资源。
  • 升级:读取锁能否优于其他其他正在等待的读线程和写线程而升级成一个写入锁?在大多数的读写锁实现中并不支持升级,因为如果没有显式的升级操作,那么很容易造成死锁。

ReentrantReadWriteLock为这两种锁都提供了可重入的加锁语义。默认非公平锁,公平锁时,等待最长时间的线程将优先获取锁。如果这个锁由读线程持有,而另一个线程请求写入锁,那么其他读线程都不能获取读取锁,知道写线程使用完并且释放了写入锁。在非公平锁中,线程获得访问许可的顺序是不确定的。写线程降级为读线程是可以的,但从读线程升级为写线程则是不可以的。

用读写锁来包装Map

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();
        }
    }
    public V get(Object key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值