读写锁的内部包含两把锁:一把是读(操作)锁,是一种共享锁;另一把是写(操作)锁,是一种独占锁。在没有写锁的时候,读锁可以被多个线程同时持有。写锁是具有排他性的:如果写锁被一个线程持有,其他的线程不能再持有写锁,抢占写锁会阻塞;进一步来说,如果写锁被一个线程持有,其他的线程不能再持有读锁,抢占读锁也会阻塞。

读写锁的读写操作之间的互斥原则具体如下:

读操作、读操作能共存,是相容的。读操作、写操作不能共存,是互斥的。写操作、写操作不能共存,是互斥的。与单一的互斥锁相比,组合起来的读写锁允许对于共享数据进行更大程度的并发操作,虽然每次只能有一个写线程,但是同时可以有多个线程并发地读数据,读写锁适用于读多写少的并发情况。

代码语言:javascript复制public interface ReadWriteLock {

/**

* 返回读锁

*/

Lock readLock();

/**

* 返回写锁

*/

Lock writeLock();

}读写锁ReentrantReadWriteLock通过ReentrantReadWriteLock类能获取读锁和写锁,它的读锁是可以多线程共享的共享锁,而它的写锁是排他锁,在被占时不允许其他线程再抢占操作。然而其读锁和写锁之间是有关系的:同一时刻不允许读锁和写锁同时被抢占,二者之间是互斥的。

读写锁升级与降级锁升级是指读锁升级为写锁,锁降级指的是写锁降级为读锁。在ReentrantReadWriteLock读写锁中,只支持写锁降级为读锁,而不支持读锁升级为写锁。ReentrantReadWriteLock不支持读锁的升级,主要是避免死锁,例如两个线程A和B都占了读锁并且都需要升级成写锁,A升级要求B释放读锁,B升级要求A释放读锁,二者就会由于相互等待形成死锁。

代码语言:javascript复制public class ReadWriteLockTest2{

//创建一个Map,代表共享数据

final static Map MAP = new HashMap();

//创建一个读写锁

final static ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();

//获取读锁

final static Lock READ_LOCK = LOCK.readLock();

//获取写锁

final static Lock WRITE_LOCK = LOCK.writeLock();

//对共享数据的写操作

public static Object put(String key, String value){

WRITE_LOCK.lock();

try{

Print.tco(DateUtil.getNowTime()

+ " 抢占了WRITE_LOCK,开始执行write操作");

Thread.sleep(1000);

String put = MAP.put(key, value);

Print.tco( "尝试降级写锁为读锁");

//写锁降级为读锁(成功)

READ_LOCK.lock();

Print.tco( "写锁降级为读锁成功");

return put;

} catch (Exception e){

e.printStackTrace();

} finally{

READ_LOCK.unlock();

WRITE_LOCK.unlock();

}

return null;

}

//对共享数据的读操作

public static Object get(String key){

READ_LOCK.lock();

try{

Print.tco(DateUtil.getNowTime()

+ " 抢占了READ_LOCK,开始执行read操作");

Thread.sleep(1000);

String value = MAP.get(key);

Print.tco( "尝试升级读锁为写锁");

//读锁升级为写锁(失败)

WRITE_LOCK.lock();

Print.tco("读锁升级为写锁成功");

return value;

} catch (InterruptedException e){

e.printStackTrace();

} finally{

WRITE_LOCK.unlock();

READ_LOCK.unlock();

}

return null;

}

public static void main(String[] args){

//创建Runnable可执行实例

Runnable writeTarget = () -> put("key", "value");

Runnable readTarget = () -> get("key");

//创建1个写线程,并启动

new Thread(writeTarget, "写线程").start();

//创建1个读线程

new Thread(readTarget, "读线程").start();

}

}StampedLockStampedLock(印戳锁)是对ReentrantReadWriteLock读写锁的一种改进,主要的改进为:在没有写只有读的场景下,StampedLock支持不用加读锁而是直接进行读操作,最大程度提升读的效率,只有在发生过写操作之后,再加读锁才能进行读操作。StampedLock的三种模式如下:

悲观读锁:与ReadWriteLock读锁类似,多个线程可同时获取悲观读锁,悲观读锁是一个共享锁。乐观读锁:相当于直接操作数据,不加任何锁,连读锁都不要。写锁:与ReadWriteLock的写锁类似,写锁和悲观读锁是互斥的。虽然写锁与乐观读锁不会互斥,但是在数据被更新之后,之前通过乐观读锁获得的数据已经变成了脏数据。StampedLock与ReentrantReadWriteLock语义类似,不同的是,StampedLock并没有实现ReadWriteLock接口,而是定义了自己的锁操作API。

代码语言:javascript复制public class StampedLockTest{

//创建一个Map,代表共享数据

final static Map MAP = new HashMap();

//创建一个印戳锁

final static StampedLock STAMPED_LOCK = new StampedLock();

//对共享数据的写操作

public static Object put(String key, String value){

long stamp = STAMPED_LOCK.writeLock(); //尝试获取写锁的印戳

try{

Print.tco(getNowTime() + " 抢占了WRITE_LOCK,开始执行write操作");

Thread.sleep(1000);

String put = MAP.put(key, value);

return put;

} catch (Exception e){

e.printStackTrace();

} finally{

Print.tco(getNowTime() + " 释放了WRITE_LOCK");

STAMPED_LOCK.unlockWrite(stamp); //释放写锁

}

return null;

}

//对共享数据的悲观读操作

public static Object pessimisticRead(String key){

Print.tco(getNowTime() + "LOCK进入过写模式,只能悲观读");

//进入了写锁模式,只能获取悲观读锁

long stamp = STAMPED_LOCK.readLock(); //尝试获取读锁的印戳

try{

//成功获取到读锁,并重新获取最新的变量值

Print.tco(getNowTime() + " 抢占了READ_LOCK");

String value = MAP.get(key);

return value;

} finally{

Print.tco(getNowTime() + " 释放了READ_LOCK");

STAMPED_LOCK.unlockRead(stamp); //释放读锁

}

}

//对共享数据的乐观读操作

public static Object optimisticRead(String key){

String value = null;

//尝试进行乐观读

long stamp = STAMPED_LOCK.tryOptimisticRead();

if (0 != stamp){

Print.tco(getNowTime() + "乐观读的印戳值,获取成功");

sleepSeconds(1); //模拟耗费时间1秒

value = MAP.get(key);

} else // 0 == stamp 表示当前为写锁模式 {

Print.tco(getNowTime() + "乐观读的印戳值,获取失败");

//LOCK已经进入写模式,使用悲观读方法

return pessimisticRead(key);

}

//乐观读操作已经间隔了一段时间,期间可能发生写入

//所以,需要验证乐观读的印戳值是否有效,即判断LOCK是否进入过写模式

if (!STAMPED_LOCK.validate(stamp)){

//乐观读的印戳值无效,表明写锁被占用过

Print.tco(getNowTime() + " 乐观读的印戳值,已经过期");

//写锁已经被抢占,进入了写锁模式,只能通过悲观读锁再一次读取最新值

return pessimisticRead(key);

} else {

//乐观读的印戳值有效,表明写锁没有被占用过

//不用加悲观读锁而直接读,减少了读锁的开销

Print.tco(getNowTime() + " 乐观读的印戳值,没有过期");

return value;

}

}

public static void main(String[] args) throws InterruptedException{

//创建Runnable可执行实例

Runnable writeTarget = () -> put("key", "value");

Runnable readTarget = () -> optimisticRead("key");

//创建1个写线程,并启动

new Thread(writeTarget, "写线程").start();

//创建1个读线程

new Thread(readTarget, "读线程").start();

}

}

Copyright © 2088 世界杯举办国家_世界杯中 - zbtysj.com All Rights Reserved.
友情链接