Java并发编程实战 第13章 显示锁

tech2022-09-14  96

内置锁的局限性:无法中断一个正在等待获取锁的线程,或者无法在请求获取一个锁时无限的等待下去。内置锁必须在获取该锁的代码块中释放,这就简化了编码工作,并且与异常处理操作实现了很好的交互,但却无法实现非阻塞结构的加锁规则。这都是使用synchronnized的原因。

13.1 Lock与ReentrantLock

必须在finally中释放锁

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

如果没有使用finally来释放Lock,将很难追踪到最初发生错误的位置。这就是ReentrantLock不能完全替代synchronized的原因,更危险。

13.1.1 轮训锁与定时锁

可定时的与可轮询锁由tryLock实现,与无条件的锁获取方式相比,有更完善的错误恢复机制。内置锁中的死锁唯一恢复方法就是重启,防止的唯一方法就是构造程序时避免出现不一致的锁顺序。并且可以防止死锁。 这种方式,控制权在自己手里。休眠的时间包括固定部分和随机部分,从而降低活锁的可能性。

13.1.2 可中断的锁获取操作

lock.lockInterruptibly(); try { return ***; } finally { lock.unlock(); }

13.1.1 非块结构的加锁

内置锁的获取和释放操作都是基于代码块的,不太灵活,容易出错。

13.2 性能考虑因素

如果有越多的资源被耗费在锁的管理和调度上,那么应用程序得到的资源越少。锁的实现方式越好,将需要越少的系统调用和上下文切换,并且共享内存总线上的内存同步通信量也越少。 Java5中内置锁低于ReentrantLock Java6中内置锁改进了与ReentrantLock类似的算法

13.3 公平性

公平 非公平:当一个线程请求非公平锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有等待线程并获得这个锁。

非公平锁的性能高于公平锁性能(两个数量级)的一个原因:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。 A持有,B挂起;A释放,B唤醒,尝试获取,同时C请求锁。存在极大可能C在B完全唤醒之前,获得、使用以及释放这个锁。达到双赢:B获得锁没有推迟,C更早的获得了锁,吞吐量提高。

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

ReentrantLock默认非公平,内置锁也不提供确定的公平性保证,只实现了统计学上的公平。Java语言规范并没有要求JVM以公平的方式实现内置锁,事实上各JVM也没有这样做。ReentrantLock并没有进一步降低锁的公平性,而只是使一些已经存在内容更明显。

13.4 在synchronized和ReentrantLock中选择

除了需要可定时的、可轮询的与可中断的锁获取操作、公平队列以及非块结构的锁,使用ReentrantLock,其它应该优先使用synchronized。

13.5 读-写锁

ReentrantLock是一种标准的互斥锁:每次只有一个线程能持有。过于强硬的加锁规则,限制了并发性。 互斥是一种保守的加锁策略,可以避免写写冲突和写读冲突,同样也避免了读读冲突。

如果放宽加锁需求,允许多个执行读操作的线程同时访问,那么将提升性能。读-写锁:一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行。 ReadWriteLock lock = new ReenTrantReadWriteLock(); ReenTrantReadWriteLock对这两种锁都提供了*可重入**的加锁语义。默认也是非公平的。

最新回复(0)