2022-09-25并发编程系列00
请注意,本文编写于 747 天前,最后修改于 747 天前,其中某些信息可能已经过时。

StampedLock

参考文档

基本介绍

该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合 使用

StampedLock提供三种模式的读写锁,分别为写锁、悲观读锁、乐观读锁。并且是写写互斥、读写互斥、读读共享。

StampedLock 独占写锁:writeLock

writeLock,是排它锁、不可重入锁、也叫独占锁,相同时间只能有一个线程获取锁,其他线程请求读锁和写锁都会被阻塞,当前没有线程持有读锁或写锁的时候才可以获得获取到该锁。

tryWriteLockwriteLock 类似,唯一的区别就是它非阻塞的特性,当获取不到锁时不会阻塞线程但是会返回一个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

可以看到, 先获取的乐观读锁, 在中间执行了写操作以后(获取了写锁) , 乐观读锁升级成了读锁

本文作者:王天赐

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!