深入了解Java并发——《Java Concurrency in Practice》13.显式锁

14 篇文章 0 订阅

需要支持轮询、定时的锁?需要可中断锁获取操作的锁?在非块结构中想要使用锁?了解支持更高级操作的显式锁。

Java 5.0新增

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

13.1 Lock与ReentrantLock

ReentrantLock实现了Lock接口,提供了与synchronized相同的互斥性和内存可见性。在获取ReentrantLock时,有着与进入同步代码块相同的语义,在释放ReentrantLock时,同样有着与退出同步代码块相同的内存语义。此外,与synchronized一样,ReentrantLock还提供了可重入的加锁语义。ReentrantLock支持在Lock接口中定义的所有获取锁模式,并且与synchronized相比,他还为处理锁的不可永兴问题提供了更高的灵活性。

内置锁必须在获取该锁的代码块中释放,这就简化了编码工作,并且与异常处理操作实现了很好的交互,但却无法实现非阻塞结构的加锁规则。这些都是使用synchronized的原因,但在某些情况下,一种更灵活的加锁机制通常能提供更好的活跃性或性能。

ReentrantLock不能完全替代synchronized的原因是,它更加危险,因为当程序的执行控制离开被保护的代码块时,不会自动清除锁。

13.1.1 轮询锁与定时锁

可定时的与可轮询的锁获取模式是由tryLock方法实现的,与无条件的锁获取模式相比,它具有更完善的错误恢复机制。

如果不能获取所有需要的锁,那么可以使用可定时的或可轮询的锁获取方式,从而重新获得控制权,它会释放已经获得的锁,然后重新尝试获取所有锁。

在实现具有时间限制的操作时,定时锁同样非常有用。当在带有时间限制的操作中调用了一个阻塞方法时,它能根据剩余事件来提供一个时限。如果操作不能在指定的时间内给出结果,那么就会使程序提前结束。使用内置锁时,开始请求锁后,这个操作将无法取消,因此内置锁很难时限带有时间限制的操作。

13.1.2 可中断的锁获取操作

可中断的锁获取操作能在可取消的操作中使用加锁。LockInterruptibly方法能够在获得锁的同时保持对中断的相应,并且由于它包含在Lock中,因此无需创建其他类型的不可中断阻塞机制。

可中断的锁获取操作的标准结构比普通的锁获取操作略微复杂些,因为需要两个try块。(如果在可中断的锁获取操作中抛出了InterruptedException,那么可以使用标准的try-finally加锁模式)

13.1.3 非块结构的加锁

为每个链表节点使用一个独立的锁,使不同的线程能独立的对链表的不同部分进行操作。每个节点的锁将保护链接指针以及在该节点中存储的数据,因此当遍历或修改链表时,必须持有该节点上的这个锁,知道获得了下一个节点的锁,只有这样,才能释放前一个节点上的锁。这种加锁方式又称为连锁式加锁 Hand-Over-Hand Locking 或 锁耦合 Lock Coupling。

13.2 性能考虑因素

在Java5.0中,当从单线程(无竞争)变化到多线程时,内置锁的性能将急剧下降,而ReentrantLock的性能下降则更为平缓,因而它具有更好的可伸缩性。但在Java6中完全不同,内置锁不会急剧下降,内置锁与显式锁的可伸缩性也基本相当。

13.3 公平性

ReentrantLock构造函数中提供了两种公平性选择:非公平锁(默认)和公平锁。在公平的锁上,线程将按照他们发出请求的顺序来获取锁,但在非公平的锁上,允许插队。当一个线程请求非公平锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。非公平的ReentrantLock并不提倡插队行为,但无法防止某个线程在合适的时候进行插队。在公平的锁中,如果有另一个线程持有这个锁或者由其他线程在队列中等待这个锁,那么新发出请求的线程将被放入队列中。

在激烈竞争的情况下,非公平锁的性能高于公平锁。其中一个原因是在恢复一个被挂起的线程与该线程真正开始运行之前存在着严重的延迟。

当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁。在这些情况下,插队带来的吞吐量提升则可能不会出现。

13.4 在synchronized和ReentrantLock之间进行选择

ReentrantLock在加锁和内存上提供的语义与内置锁相同,此外它还提供了一些其他功能,包括定时的锁等待、可中断的锁等待、公平性,以及实现非块结构的加锁

与显示锁相比,内置锁仍然具有很大的优势。内置锁为许多开发人员所熟悉。ReentrantLock的危险性比同步机制要高,如果忘记在finally块中调用unlock,那么就埋下了重大的隐患。

仅当内置锁不能满足需求时,才可以考虑使用ReentrantLock。这些需求包括:可定时的、可轮询的、可中断的锁获取操作,公平队列,非块结构的锁。除此之外,还是应该优先使用synchronized。

13.5 读-写锁

ReentrantLock实现了一种标准的互斥锁:每次最多只有一个线程能持有ReentrantLock。但对于维护数据的完整性来说,互斥通常是一种过于强硬的加锁规则,因此也就不必要的限制了并发性。在许多情况下,数据结构上的操作都是读操作,如果能够放宽加锁需求,允许多个执行读操作的线程同时访问数据结构,那么将提升程序的性能。

读/写锁解决了这个问题:一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行。

读-写锁是一种性能优化措施,在一些特定的情况下能实现更高的并发性。在实际情况中,对于在多处理器系统上被频繁读取的数据结构,读-写锁能够提高性能。而在其他情况下,读-写锁的性能比独占锁的性能要略差一些,因为它的复杂性更高。

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

ReentrantReadWriteLock中的写锁只能有唯一的所有者,并且只能由获得写锁的线程释放。

当锁的持有时间较长且大部分操作都不会修改被守护的资源时,读-写锁能提高并发性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值