JUC-ReentrantReadWriteLock

ReentrantReadWriteLock:读写锁,一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享。

「读写锁ReentrantReadWrteLock] 并不是真正意义上的读写分离,它只允许读读共存,而读写和写写依然是互斥的,大多实际场景是“读/读”线程问并不存在互斤关系,只有”读/写”线程或写/写”线程间的操作需要互斥的。因此引入ReentrantReadwriteLock。
一个ReentrantReadWriteLock同时只能存在一个写锁但是可以存在多个读锁,但不能同时存在写锁和读锁。也即一个资源可以被多个读操作访问或一个写操作访问,但两者不能同时进行
只有在读多写少情境之下,读写锁才具有较高的性能体现。

ReentrantReadWriteLock特点:1、可重入  2、读写分离  


ReentrantReadWriteLock的缺点:

        1、锁饥饿,写线程可能获得不到锁

        2、如果有线程在读,那么写线程是无法获得写锁的,是悲观锁的策略

ReentrantReadWriteLock的锁降级:

锁降级:遵循获取写锁一再获取读锁→再释放写锁的次序,写锁能够降级成为读锁。
如果一个线程占有了写锁,在不释放写锁的情况下,它还能占有读锁,即写锁降级为读锁。

不能从读锁升级到写锁。

锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性。

1写完了,我希望大家马上拿到这个1.0版本,你们立刻来读取。
这样,就要立刻加读锁,
再读的过程中,不可以让其它写线程进来修改数据。
保证我们的1.0版,可以被其它线程完整读取后再进行下一次修改

写锁和读锁是互斥的(这里的互序是指线程间的互斥,当前线程可以获取到写锁又获取到读锁,但是获取到了读锁不能继续获取写锁),这是因为读写锁要保证写操作的可见性。
因为,如果允许读锁在被获取的情况下获取写锁,那么正在运行的其他读线程无法感知到当前写线程的操作。
因此,
分析读写锁ReentrantReadWriteLock,会发现它有个潜在的问题:
读锁全完,写锁有望;写锁独占,读写全堵;
如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,
即ReadWriteLock读的过程中不允许写,只有等待线程都释放了读锁,当前线程才能获取写锁
也就是写入必须等待,这是一种悲观的读锁。人家还在读着那,你先别去写,省的数据乱。

===后续讲解StampedLock时再详细展开==
分析StampedLock(后面详细讲解,会发现它改进之处在于:

读的过程中也允许获取写锁介入,这样会导致我们读的数据就可能不一致!
所以,需要额外的方法来判断读的过程中是否有写入,这是一种乐观的读锁。
显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。

1、代码中声明了一个volatile类型的cacheValid变量,保证其可见性。
2、首先获取读锁,如果cache不可用,则释放读锁,获取写锁,在更改数据之前,再检查一次cacheValid的值,然后修改数据,将cacheValid置为true,然后在释放写锁前获取读锁;此时,cache中数据可用,处理cache中数据,最后释放读锁。这个过程就是一个完整的锁降级的过程,
目的是保证数据可见性。


如果违背锁降级的步骤
如果当前的线程C在修改完cache中的数据后,没有获取读锁而是直接释放了写锁,那么假设此时另一个线程D获取了写锁并修改了数据,那么C线程无法感知到数据已被修改,则数据出现错误。


如果遵循锁降级的步骤
线程C在释放写锁之前获取读锁,那么线程D在获取写锁时将被阻塞,直到线程C完成数据处理过程,释放读锁。这样可以保证返回的数据是这次更新的数据,该机制是专门为了缓存设计的。
 

猜你喜欢

转载自blog.csdn.net/qq_39940205/article/details/120809077
JUC
今日推荐