该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合 戳 使用
StampedLock提供三种模式的读写锁,分别为写锁、悲观读锁、乐观读锁。并且是写写互斥、读写互斥、读读共享。
writeLock
,是排它锁、不可重入锁、也叫独占锁,相同时间只能有一个线程获取锁,其他线程请求读锁和写锁都会被阻塞,当前没有线程持有读锁或写锁的时候才可以获得获取到该锁。
tryWriteLock
和 writeLock
类似,唯一的区别就是它非阻塞的特性,当获取不到锁时不会阻塞线程但是会返回一个stamp = 0的标识。
stamp > 0
表示成功获取到锁;stamp = 0
表示未获取到锁,但不会阻塞线程 想要开锁(释放锁)必须使用对应的钥匙(stamp)
读锁与读锁是可以共享的, 不会互斥
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() 方法(乐观读),读取完毕后需要做一次 戳校验 如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。
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
可以看到, 先获取的乐观读锁, 在中间执行了写操作以后(获取了写锁) , 乐观读锁升级成了读锁
本文作者:王天赐
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!