锁专题(六)这么用心的可重入读写锁讲解,不给作者点个赞吗?


锁专题(六)这么用心的可重入读写锁讲解,不给作者点个赞吗?文章插图
思维导图
ReentrantLock 锁回顾在Java5.0之前,只有synchronized(内置锁)和volatile. Java5.0后引入了显示锁ReentrantLock.
ReentrantLock是可重入的锁,它不同于内置锁, 它在每次使用都需要显示的加锁和解锁, 而且提供了更高级的特性:公平锁, 定时锁, 有条件锁, 可轮询锁, 可中断锁. 可以有效避免死锁的活跃性问题
Lock 接口 public interface Lock {//阻塞直到获得锁或者中断void lock();//阻塞直到获得锁或者中断抛异常void lockInterruptibly() throws InterruptedException;//只有锁可用时才获得,否则直接返回boolean tryLock();//只有锁在指定时间内可用时才获得,否则直接返回,中断时抛异常boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();//返回一个绑定在这个锁上的条件Condition newCondition();}使用Lock lock = new ReentrantLock();lock.lock();try{//更新对象状态}finally{//这里注意,一定要有finally代码块去解锁//否则容易造成死锁等活跃性问题lock.unlock();}轮询锁的和定时锁可轮询和可定时的锁请求是通过tryLock()方法实现的,和无条件获取锁不一样.
ReentrantLock可以有灵活的容错机制.死锁的很多情况是由于顺序锁引起的, 不同线程在试图获得锁的时候阻塞,并且不释放自己已经持有的锁, 最后造成死锁.
tryLock()方法在试图获得锁的时候,如果该锁已经被其它线程持有,则按照设置方式立刻返回,而不是一直阻塞等下去,同时在返回后释放自己持有的锁.
可以根据返回的结果进行重试或者取消,进而避免死锁的发生.
公平性ReentrantLock构造函数中提供公平性锁和非公平锁(默认)两种选择 。
所谓公平锁 , 线程将按照他们发出请求的顺序来获取锁 , 不允许插队;但在非公平锁上 , 则允许插队:当一个线程发生获取锁的请求的时刻 , 如果这个锁是可用的 , 那这个线程将跳过所在队列里等待线程并获得锁 。
我们一般希望所有锁是非公平的 。 因为当执行加锁操作时 , 公平性将讲由于线程挂起和恢复线程时开销而极大的降低性能 。 考虑这么一种情况:A线程持有锁 , B线程请求这个锁 , 因此B线程被挂起;A线程释放这个锁时 , B线程将被唤醒 , 因此再次尝试获取锁;与此同时 , C线程也请求获取这个锁 , 那么C线程很可能在B线程被完全唤醒之前获得、使用以及释放这个锁 。 这是种双赢的局面 , B获取锁的时刻(B被唤醒后才能获取锁)并没有推迟 , C更早地获取了锁 , 并且吞吐量也获得了提高 。
在大多数情况下 , 非公平锁的性能要高于公平锁的性能 。
可中断获锁获取操作lockInterruptibly() 方法能够在获取锁的同时保持对中断的响应 , 因此无需创建其它类型的不可中断阻塞操作 。
对于可重入锁的回顾就到这里 , 更深入地讲解 , 可以阅读
ReentrantLock 可重入锁这样学 , 面试没烦恼
锁专题(五)ReentrantLock 深入源码详解
为什么需要读写锁在Java并发包中常用的锁(如:ReentrantLock) , 基本上都是排他锁 , 这些锁在同一时刻只允许一个线程进行访问 , 而读写锁在同一时刻可以允许多个读线程访问 , 但是在写线程访问时 , 所有的读线程和其他写线程均被阻塞 。
【锁专题(六)这么用心的可重入读写锁讲解,不给作者点个赞吗?】读写锁维护了一对锁 , 一个读锁和一个写锁 , 通过分离读锁和写锁 , 使得 并发性相比一般的排他锁有了很大提升 。
除了保证写操作对读操作的可见性以及并发性的提升之外 , 读写锁能够简化读写交互场景的编程方式 。
锁专题(六)这么用心的可重入读写锁讲解,不给作者点个赞吗?文章插图
假设在程序中定义一个共享的数据结构用作缓存 , 它大部分时间提供读服务(例如:查询和搜索) , 而写操作占有的时间很少 , 但是写操作完成之后的更新需要对后续的读服务可见 。
在没有读写锁支持的(Java 5 之前)时候 , 如果需要完成上述工作就要使用Java的等待通知机制 , 就是当写操作开始时 , 所有晚于写操作的读操作均会进入等待状态 , 只有写操作完成并进行通知之后 , 所有等待的读操作才能继续执行(写操作之间依靠synchronized关键字进行同步) , 这样做的目的是使读操作都能读取到正确的数据 , 而不会出现脏读 。
改用读写锁实现上述功能 , 只需要在读操作时获取读锁 , 而写操作时获取写锁即可 , 当写锁被获取到时 , 后续(非当前写操作线程)的读写操作都会被 阻塞 , 写锁释放之后 , 所有操作继续执行 , 编程方式相对于使用等待通知机制的实现方式而言 , 变得简单明了 。