StampedLock
参考文档
基本介绍
该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合 戳 使用
StampedLock提供三种模式的读写锁,分别为写锁、悲观读锁、乐观读锁。并且是写写互斥、读写互斥、读读共享。
StampedLock 独占写锁:writeLock
writeLock
,是排它锁、不可重入锁、也叫独占锁,相同时间只能有一个线程获取锁,其他线程请求读锁和写锁都会被阻塞,当前没有线程持有读锁或写锁的时候才可以获得获取到该锁。
tryWriteLock
和 writeLock
类似,唯一的区别就是它非阻塞的特性,当获取不到锁时不会阻塞线程但是会返回一个stamp = 0的标识。
stamp > 0
表示成功获取到锁;stamp = 0
表示未获取到锁,但不会阻塞线程 想要开锁(释放锁)必须使用对应的钥匙(stamp)
- StampedLock 不支持条件变量
- StampedLock 不支持可重入
基本使用
加读锁
读锁与读锁是可以共享的, 不会互斥
package cn.knightzz.stamped_lock.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
import static java.lang.Thread.sleep;
/**
* @author 王天赐
* @title: StampedLockTest
* @projectName hm-juc-codes
* @description: StampedLock测试
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-09-22 14:43
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.StampedWriteLockTest")
public class StampedReadLockTest {
public static void main(String[] args) throws InterruptedException {
StampedLock stampedLock = new StampedLock();
// 获取写锁
Thread t1 = new Thread(() -> {
long stamp = 0;
try {
log.debug("尝试获取锁 ... ");
stamp = stampedLock.readLock();
log.debug("获取锁成功 ... ");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
log.debug("释放锁成功 ... ");
stampedLock.unlockRead(stamp);
}
}, "t1");
Thread t2 = new Thread(() -> {
long stamp = 0;
try {
log.debug("尝试获取锁 ... ");
stamp = stampedLock.readLock();
log.debug("获取锁成功 ... ");
} finally {
stampedLock.unlockRead(stamp);
}
}, "t2");
t1.start();
t2.start();
}
}
执行结果如下 :
17:54:10.355 [t1] DEBUG c.StampedWriteLockTest - 尝试获取锁 ...
17:54:10.355 [t2] DEBUG c.StampedWriteLockTest - 尝试获取锁 ...
17:54:10.355 [t2] DEBUG c.StampedWriteLockTest - 获取锁成功 ...
17:54:10.355 [t1] DEBUG c.StampedWriteLockTest - 获取锁成功 ...
17:54:11.358 [t1] DEBUG c.StampedWriteLockTest - 释放锁成功 ...
Process finished with exit code 0
可以看到上面的运行结果, t1 和 t2线程同时获取锁 , 说明读锁是不互斥的
加写锁
- 写锁与写锁是互斥的
- 读锁与写锁也是互斥的
package cn.knightzz.stamped_lock.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
import static java.lang.Thread.sleep;
/**
* @author 王天赐
* @title: StampedLockTest
* @projectName hm-juc-codes
* @description: StampedLock测试
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-09-22 14:43
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.StampedWriteLockTest")
public class StampedWriteLockTest {
public static void main(String[] args) throws InterruptedException {
StampedLock stampedLock = new StampedLock();
// 获取写锁
Thread t1 = new Thread(() -> {
long stamp = 0;
try {
log.debug("尝试获取锁 ... ");
stamp = stampedLock.writeLock();
log.debug("获取锁成功 ... ");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
log.debug("释放锁成功 ... ");
stampedLock.unlockWrite(stamp);
}
}, "t1");
Thread t2 = new Thread(() -> {
long stamp = 0;
try {
log.debug("尝试获取锁 ... ");
stamp = stampedLock.writeLock();
log.debug("获取锁成功 ... ");
} finally {
stampedLock.unlockWrite(stamp);
}
}, "t2");
t1.start();
sleep(100);
t2.start();
}
}
乐观读
乐观读,StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次 戳校验 如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。
- 执行 tryOptimisticRead 时并没有加锁
- 而是当其他线程执行 读操作的时候, 才会升级成读锁
package cn.knightzz.stamped_lock.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.StampedLock;
/**
* @author 王天赐
* @title: StampOptimisticLockTest
* @projectName hm-juc-codes
* @description: 乐观锁
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-09-22 18:23
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.StampOptimisticLockTest")
public class StampOptimisticLockTest {
public static void main(String[] args) {
//乐观读, StampedLock 支持 tryOptimisticRead()方法, 乐观读,读取完毕后需要做一次戳校验
// 如果校验通过, 表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全
StampedLock lock = new StampedLock();
// 获取 stamp
long stamp01 = lock.tryOptimisticRead();
log.debug("stamp01 {}", stamp01);
// 获取写锁
long stamp02 = lock.writeLock();
log.debug("stamp02 {}", stamp02);
if (!lock.validate(stamp01)) {
// 锁升级
log.debug("校验未通过, 需要重新获取读锁!");
} else {
log.debug("校验通过, 数据可以安全使用!");
}
lock.unlock(stamp02);
}
}
执行结果如下 :
09:30:47.891 [main] DEBUG c.StampOptimisticLockTest - stamp01 256
09:30:47.894 [main] DEBUG c.StampOptimisticLockTest - stamp02 384
09:30:47.894 [main] DEBUG c.StampOptimisticLockTest - 校验未通过, 需要重新获取读锁!
Process finished with exit code 0
可以看到上面的运行结果, 在执行乐观读以后, 再次执行获取写锁, stamp 获取读锁以后 stamp 的值发生了改变, 进行校验时校验失败
**DataContainerStamped : **
package cn.knightzz.stamped_lock.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.StampedLock;
import static java.lang.Thread.sleep;
/**
* @author 王天赐
* @title: DataContainerStamped
* @projectName hm-juc-codes
* @description:
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-09-23 10:06
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.DataContainerStamped")
public class DataContainerStamped {
private int data;
private final StampedLock lock = new StampedLock();
public DataContainerStamped(int data) {
this.data = data;
}
public int read(int readTime) throws InterruptedException {
long stamp = lock.tryOptimisticRead();
log.debug("optimistic read locking...{}", stamp);
sleep(readTime);
if (lock.validate(stamp)) {
log.debug("read finish...{}, data:{}", stamp, data);
return data;
}
// 锁升级 - 读锁
log.debug("updating to read lock... {}", stamp);
try {
stamp = lock.readLock();
log.debug("read lock {}", stamp);
sleep(readTime);
log.debug("read finish...{}, data:{}", stamp, data);
return data;
} finally {
log.debug("read unlock {}", stamp);
lock.unlockRead(stamp);
}
}
public void write(int newData) {
long stamp = lock.writeLock();
log.debug("write lock {}", stamp);
try {
sleep(2);
this.data = newData;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
log.debug("write unlock {}", stamp);
lock.unlockWrite(stamp);
}
}
}
读取数据的时候, 先执行乐观读, 如果校验失败再升级成读锁
测试代码 :
package cn.knightzz.stamped_lock.test;
import lombok.extern.slf4j.Slf4j;
import static cn.hutool.core.thread.ThreadUtil.sleep;
/**
* @author 王天赐
* @title: DataContainerStampedTest
* @projectName hm-juc-codes
* @description:
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-09-23 10:08
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.DataContainerStampedTest")
public class DataContainerStampedTest {
public static void main(String[] args) {
DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread(() -> {
try {
dataContainer.read(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1").start();
sleep(0.5);
new Thread(() -> {
try {
dataContainer.read(0);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t2").start();
}
}
执行结果如下
10:13:51.291 [t1] DEBUG c.DataContainerStamped - optimistic read locking...256
10:13:51.291 [t2] DEBUG c.DataContainerStamped - optimistic read locking...256
10:13:51.299 [t2] DEBUG c.DataContainerStamped - read finish...256, data:1
10:13:51.301 [t1] DEBUG c.DataContainerStamped - read finish...256, data:1
Process finished with exit code 0
可以看到乐观读锁是同时获取的,可以并发执行
测试写读
package cn.knightzz.stamped_lock.test;
import lombok.extern.slf4j.Slf4j;
import static cn.hutool.core.thread.ThreadUtil.sleep;
/**
* @author 王天赐
* @title: DataContainerStampedTest
* @projectName hm-juc-codes
* @description:
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-09-23 10:08
*/
@SuppressWarnings("all")
@Slf4j(topic = "c.DataContainerStampedTest")
public class DataContainerStampedTest {
public static void main(String[] args) {
DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread(() -> {
try {
dataContainer.read(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1").start();
sleep(0.5);
new Thread(() -> {
dataContainer.write(100);
}, "t2").start();
}
}
执行结果如下 :
14:32:44.289 [t1] DEBUG c.DataContainerStamped - optimistic read locking...256
14:32:44.289 [t2] DEBUG c.DataContainerStamped - write lock 384
14:32:44.294 [t1] DEBUG c.DataContainerStamped - updating to read lock... 256
14:32:44.295 [t2] DEBUG c.DataContainerStamped - write unlock 384
14:32:44.295 [t1] DEBUG c.DataContainerStamped - read lock 513
14:32:44.297 [t1] DEBUG c.DataContainerStamped - read finish...513, data:100
14:32:44.297 [t1] DEBUG c.DataContainerStamped - read unlock 513
Process finished with exit code 0
可以看到, 先获取的乐观读锁, 在中间执行了写操作以后(获取了写锁) , 乐观读锁升级成了读锁