ReentrantReadWriteLock
是ReadWriteLock接口的实现类。ReadWriteLock接口的核心方法是readLock(),writeLock()。实现了并发读、互斥写。但读锁会阻塞写锁,是悲观锁的策略。
JDK1.8下,如图ReentrantReadWriteLock有5个静态方法:
Sync:继承于经典的AbstractQueuedSynchronizer(传说中的AQS),是一个抽象类,包含2个抽象方法readerShouldBlock();writerShouldBlock()
FairSync和NonfairSync:继承于Sync,分别实现了公平/非公平锁。
如果读取执行情况很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程吃吃无法竞争到锁定而一直处于等待状态。
应用伪代码:
package concurrency.example.lock;import concurrency.annotations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;/**Created by William on 2018/4/26 0026*ReentrantReadWriteLock,悲观读,有写就不给读*/
@Slf4j
@ThreadSafe
public class ReentrantReadWriteExample1 {private final Map map &#61; new TreeMap<>();private final ReentrantReadWriteLock lock &#61; new ReentrantReadWriteLock();private final Lock readLock &#61; lock.readLock();private final Lock writeLock &#61; lock.writeLock();private final static Lock reentrantLock &#61; new ReentrantLock();public Data get(String key){readLock.lock();try {return map.get(key);}finally {readLock.unlock();}}public Set getAllKeys(){readLock.lock();try {return map.keySet();}finally {readLock.unlock();}}public Data put(String key, Data value){writeLock.lock();try {return map.put(key,value);}finally {writeLock.unlock();}}class Data{}
}
StampedLock
StampedLock控制锁有三种模式&#xff08;写&#xff0c;读&#xff0c;乐观读&#xff09;&#xff0c;一个StampedLock状态是由版本和模式两个部分组成&#xff0c;锁获取方法返回一个数字作为票据stamp&#xff0c;它用相应的锁状态表示并控制访问&#xff0c;数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。
所谓的乐观读模式&#xff0c;也就是若读的操作很多&#xff0c;写的操作很少的情况下&#xff0c;你可以乐观地认为&#xff0c;写入与读取同时发生几率很少&#xff0c;因此不悲观地使用完全的读取锁定&#xff0c;程序可以查看读取资料之后&#xff0c;是否遭到写入执行的变更&#xff0c;再采取后续的措施&#xff08;重新读取变更信息&#xff0c;或者抛出异常&#xff09; &#xff0c;这一个小小改进&#xff0c;可大幅度提高程序的吞吐量&#xff01;&#xff01;
应用代码&#xff1a;
package concurrency.example.lock;import java.util.concurrent.locks.StampedLock;/*** 乐观读锁&#xff0c;读操作大量&#xff0c;写操作少的时候&#xff0c;可以使用该锁* 源码例子*/
public class StampedLockExample1 {class Point {private double x, y;private final StampedLock sl &#61; new StampedLock();void move(double deltaX, double deltaY) { // an exclusively locked methodlong stamp &#61; sl.writeLock();try {x &#43;&#61; deltaX;y &#43;&#61; deltaY;} finally {sl.unlockWrite(stamp);}}//下面看看乐观读锁案例double distanceFromOrigin() { // A read-only methodlong stamp &#61; sl.tryOptimisticRead(); //获得一个乐观读锁double currentX &#61; x, currentY &#61; y; //将两个字段读入本地局部变量if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生&#xff1f;stamp &#61; sl.readLock(); //如果没有&#xff0c;我们再次获得一个读悲观锁try {currentX &#61; x; // 将两个字段读入本地局部变量currentY &#61; y; // 将两个字段读入本地局部变量} finally {sl.unlockRead(stamp);}}return Math.sqrt(currentX * currentX &#43; currentY * currentY);}//下面是悲观读锁案例void moveIfAtOrigin(double newX, double newY) { // upgrade// Could instead start with optimistic, not read modelong stamp &#61; sl.readLock();try {while (x &#61;&#61; 0.0 && y &#61;&#61; 0.0) { //循环&#xff0c;检查当前状态是否符合long ws &#61; sl.tryConvertToWriteLock(stamp); //将读锁转为写锁if (ws !&#61; 0L) { //这是确认转为写锁是否成功stamp &#61; ws; //如果成功 替换票据x &#61; newX; //进行状态改变y &#61; newY; //进行状态改变break;} else { //如果不能成功转换为写锁sl.unlockRead(stamp); //我们显式释放读锁stamp &#61; sl.writeLock(); //显式直接进行写锁 然后再通过循环再试}}} finally {sl.unlock(stamp); //释放读锁或写锁}}}
}
总结
1.当只有少量的锁时候&#xff0c;syncronized是很好的实现&#xff0c;不会死锁&#xff1b;
2.竞争者不少&#xff0c;锁数量的增长我们是可以预估的&#xff0c;ReenTrantLock是很好的实现&#xff1b;