22、一个带有邮戳的锁StampedLock(jdk1.8出现)

本文深入探讨了JDK1.8中引入的StampedLock,一种解决传统锁机制缺陷的新锁类型。文章对比了ReentrantReadWriteLock、ReentrantLock和synchronized锁,分析了StampedLock的三种模式:写入、读取和乐观读取,以及其实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

jdk1.8真的可以作为一个宝藏,随便一个新的特性都足以写一本书,今天分析一个在jdk1.8中,引入的一个新的带有邮戳的StampedLock。这篇文章主要从使用的角度来分析一下:

一、为什么会需要StampedLock?

任何一个新引入的知识都是为了解决以往系统中出现的问题,否则新引入的将变得毫无价值。我曾经写过一些关于ReentrantReadWriteLock, ReentrantLock 和synchronized锁的文章。如果你之前了解过这些锁或者在工作中使用过,你会发现他们都有各种各样的缺点。

比如synchronized不可中断等,ReentrantLock 未能读写分离实现,虽然ReentrantReadWriteLock能够读写分离了,但是对于其写锁想要获取的话,就必须没有任何其他读写锁存在才可以,这实现了悲观读取。而且如果读操作很多,写很少的情况下,线程有可能遭遇饥饿问题。

饥饿问题:ReentrantReadWriteLock实现了读写分离,想要获取读锁就必须确保当前没有其他任何读写锁了,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,因为当前有可能会一直存在读锁。而无法获得写锁。

这时候怎么办呢?于是在jdk1.8的时候引入了一个新的锁StampedLock。

二、简单使用

StampedLock控制锁有三种模式(写,读,乐观读)

(1)写入(Writing):writeLock是一个独占锁,也是一个悲观锁。

(2)读取(Reading):readLock这时候是一个悲观锁。

(3)乐观读取(Optimistic Reading):提供了tryOptimisticRead方法返回一个非0的stamp,只有当前同步状态没有被写模式所占有是才能获取到。乐观读取模式仅用于短时间读取操作时经常能够降低竞争和提高吞吐量。同时使用的时候一般需要读取并存储到另外一个副本,以用做对比使用。
下面干脆使用代码来实现一下这几种锁的实现。

1、写锁的实现:悲观写

public class StampedTest {
	private static StampedLock lock = new StampedLock();
	private static List<String> data = new ArrayList<String>();
	public static void write() {
		long stamped = -1;
		try {
			stamped = lock.writeLock();
			data.add("写线程写入的数据"+stamped);
			System.out.println("写入的数据是:" + stamped);
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlockWrite(stamped);
		}
	}
}

悲观写的意思是,认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改。

2、读锁的实现:悲观读

public class StampedTest {
	private static StampedLock lock = new StampedLock();
	private static List<String> data = new ArrayList<String>();
	public static void read() {
		long stamped = -1;
		try {
			stamped = lock.readLock();
			for (String name : data) {
				System.out.println("读的数据是:" + name);
			}
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlockRead(stamped);
		}
	}
}

3、测试

public class StampedTest {
	private static StampedLock lock = new StampedLock();
	private static List<String> data = new ArrayList<String>();
	public static void main(String[] args) {
		ExecutorService executor = Executors.newFixedThreadPool(10);
		Runnable readTask = () -> {
			for (;;) {
				read();
			}
		};
		Runnable writeTask = () -> {
			for (;;) {
				write();
			}
		};
		for(int i=0;i<9;i++) {
			executor.submit(readTask);
		}
		executor.submit(writeTask);
	}
}

注意到,这三块都是在同一个类中,为了演示方便就这样分开描述了。在运行测试的时候我们会发现,读的线程比较多,但是写的线程比较少,因此读锁执行的概率比较大一些。整个流程是先写数据,然后再读数据,在读数据的时候不会执行写操作。我们来实现一下第三种,那就乐观读取。

4、读锁的实现:乐观读

public class StampedTest {
	private static StampedLock lock = new StampedLock();
	private static List<String> data = new ArrayList<String>();
	public static void optimisticRead() {
		//尝试去拿一个乐观锁
		long stamped = lock.tryOptimisticRead();
		//如果没有线程修改,我们再去获取一个读锁
		if(lock.validate(stamped)) {
			try {
				stamped = lock.readLock();
				for (String name : data) {
					System.out.println("读的数据是:" + name);
				}
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				lock.unlockRead(stamped);
			}
		}
	}
}

这时候我们定义了一个新的乐观读锁,意思是在读的时候,依然可以写入。再次运行的时候你会发现这次写操作会比之前的测试次数多了,这说明在使用乐观读的时候,也发生了写操作。

三、总结

StampedLock的调度策略对待读写操作都是公平合理的。所有try方法都是尽最大努力,调用可能会成功,也可能会失败。这个类没有直接实现Lock或者ReadWriteLock方法,源码中是把他当做一个单独的类来实现的。当然,一个StampedLock可以通过asReadLock,asWriteLock,asReadWriteLock方法来得到全部功能的子集。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值