-
读写锁定义
一个资源可以被多个读线程访问,或者被一个写线程访问,但不能同时存在读线线程。(读读共享,读写互斥)
-
锁降级
遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。《java并发编程的艺术》
可以理解为,写的权力>读的权力,写锁可以降级为读锁,但是读锁无法变成写锁。
锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性(写后立刻读,感知数据变化)。在ReentrantReadWriteLock中,当读锁被使用时,有线程尝试获得写锁将会被阻塞,直到读锁被释放,才会获得写锁。
public class LockDownGradingDemo {
public static void main(String[] args) {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
writeLock.lock();
System.out.println("Hello");
readLock.lock();
System.out.println("World");
writeLock.unlock();
readLock.unlock();
}
}
public class LockDownGradingDemo {
public static void main(String[] args) {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
readLock.lock();
System.out.println("World");
writeLock.lock();
System.out.println("Hello");
readLock.unlock();
writeLock.unlock();
}
}
-
StampedLock
JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化。 StampedLock可以读的过程中,可以写。(可能出现数据不一致现象)
-
StampedLock的特点
1、所有获得锁的方法,都返回一个邮戳(stamp),stamp为零表示获取失败,其余都表示获取成功。
2、所有释放锁的方法,都需要一个邮戳(stamp),这个stamp必须是和成功获取锁时得到的stamp一致。
3、StampedLock是不可重入的(线程持有写锁,再去获得写锁就会造成死锁)。
-
StampedLock三种访问模式
1、Reading(读模式):功能和ReentrantReadWriteLock的读锁类似
2、Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
3、Optimistic reading(乐观读模式):无锁机制,乐观认为读取时没人修改,假如被修改才进行锁升级。
public class StampedLockDemo {
static int num=1;
static StampedLock stampedLock=new StampedLock();
public void read(){
long stamp = stampedLock.readLock();
System.out.println(Thread.currentThread().getName()+" "+"读线程准备");
//暂停4秒钟线程
for (int i = 0; i <4 ; i++) {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t 正在读取中......");
}
try{
int res=num;
System.out.println(Thread.currentThread().getName()+" "+" 获得成员变量值res:" + res);
}catch (Exception e){
e.printStackTrace();
}finally {
stampedLock.unlock(stamp);
}
System.out.println(Thread.currentThread().getName()+" "+"读线程结束"+" "+"stamp: "+stamp);
}
public void write(){
long stamp = stampedLock.writeLock();
System.out.println(Thread.currentThread().getName()+" "+"写线程准备修改");
try {
num=num+11;
}catch (Exception e){
e.printStackTrace();
}finally {
stampedLock.unlock(stamp);
}
System.out.println(Thread.currentThread().getName()+" "+"写线程结束"+" "+"stamp: "+stamp);
}
public void optimistic(){
long stamp = stampedLock.tryOptimisticRead();
//先把数据取得一次
int result = num;
//间隔4秒钟,我们很乐观的认为没有其他线程修改过number值。
System.out.println("4秒前stampedLock.validate值(true无修改,false有修改)"+"\t"+stampedLock.validate(stamp));
for (int i = 1; i <=4 ; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t 正在读取中......"+i+
"秒后stampedLock.validate值(true无修改,false有修改)"+"\t"
+stampedLock.validate(stamp));
}
if(!stampedLock.validate(stamp)) {
System.out.println("有人动过--------存在写操作!");
//有人动过了,需要从乐观读切换到普通读的模式。
stamp = stampedLock.readLock();
try {
System.out.println("从乐观读 升级为 悲观读并重新获取数据");
//重新获取数据
result = num;
System.out.println("重新悲观读锁通过获取到的成员变量值result:" + result);
}catch (Exception e){
e.printStackTrace();
}finally {
stampedLock.unlockRead(stamp);
}
}
System.out.println(Thread.currentThread().getName()+"\t finally value: "+result+" "+"stamp: "+stamp);
}
public static void main(String[] args) {
StampedLockDemo resource = new StampedLockDemo();
//乐观读,失败,重新转为悲观读,重读数据一次
new Thread(() -> {
//乐观读
resource.optimistic();
},"readThread").start();
//2秒钟乐观读取resource.tryOptimisticRead()失败
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
resource.write();
},"writeThread").start();
}
}
-
StampedLock的缺点
1、StampedLock不支持可重入。
2、悲观读和写锁不支持条件变量(Condition),即signal或者await之类的条件。
3、StampedLock不能在调用中中断,即不要调用interrput()方法。