文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java利用StampedLock实现读写锁的方法详解

2022-11-13 18:29

关注

概述

想到读写锁,大家第一时间想到的可能是ReentrantReadWriteLock。实际上,在jdk8以后,java提供了一个性能更优越的读写锁并发类StampedLock,该类的设计初衷是作为一个内部工具类,用于辅助开发其它线程安全组件,用得好,该类可以提升系统性能,用不好,容易产生死锁和其它莫名其妙的问题。本文主要和大家一起学习下StampedLock的功能和使用。

StampedLock介绍

StampedLock的状态由版本和模式组成。锁获取方法返回一个戳,该戳表示并控制对锁状态的访问。StampedLock提供了3种模式控制访问锁:

1.写模式

获取写锁,它是独占的,当锁处于写模式时,无法获得读锁,所有乐观读验证都将失败。

long stamp = lock.writeLock();
try {
    ....
} finally {
    lock.unlockWrite(stamp);
}

2.读模式

悲观的方式后去非独占读锁。

long stamp = lock.readLock();
try {
    ....
} finally {
    lock.unlockRead(stamp);	    
}

3.乐观读模式

乐观读也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量。

StampedLock 支持 tryOptimisticRead() 方法,读取完毕后做一次戳校验,如果校验通过,表示这期间没有其他线程的写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据一致性。

long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
	// 锁升级
}

此外,StampedLock 提供了api实现上面3种方式进行转换:

long tryConvertToWriteLock(long stamp)

如果锁状态与给定的戳记匹配,则执行以下操作之一。如果戳记表示持有写锁,则返回它。或者,如果是读锁,如果写锁可用,则释放读锁并返回写戳记。或者,如果是乐观读,则仅在立即可用时返回写戳记。该方法在所有其他情况下返回零

long tryConvertToReadLock(long stamp)

如果锁状态与给定的戳记匹配,则执行以下操作之一。如果戳记表示持有写锁,则释放它并获得读锁。或者,如果是读锁,返回它。或者,如果是乐观读,则仅在立即可用时才获得读锁并返回读戳记。该方法在所有其他情况下返回零。

long tryConvertToOptimisticRead(long stamp)

如果锁状态与给定的戳记匹配,那么如果戳记表示持有锁,则释放它并返回一个观察戳记。或者,如果是乐观读,则在验证后返回它。该方法在所有其他情况下返回0,因此作为“tryUnlock”的形式可能很有用。

演示例子

下面用一个例子演示下StampedLock的使用,例子来源jdk中的javadoc。

@Slf4j
@Data
public class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();

    void move(double deltaX, double deltaY) throws InterruptedException {
        //涉及对共享资源的修改,使用写锁-独占操作
        long stamp = sl.writeLock();
        log.info("writeLock lock success");
        Thread.sleep(500);
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
            log.info("unlock write lock success");
        }
    }

    
    double distanceFromOrigin() throws InterruptedException {
        long stamp = sl.tryOptimisticRead();    // 使用乐观读锁
        log.info("tryOptimisticRead lock success");
        // 睡一秒中
        Thread.sleep(1000);
        double currentX = x, currentY = y;      // 拷贝共享资源到本地方法栈中
        if (!sl.validate(stamp)) {              // 如果有写锁被占用,可能造成数据不一致,所以要切换到普通读锁模式
            log.info("validate stamp error");
            stamp = sl.readLock();
            log.info("readLock success");
            try {
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);
                log.info("unlock read success");
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }

    void moveIfAtOrigin(double newX, double newY) { // upgrade
        // Could instead start with optimistic, not read mode
        long stamp = sl.readLock();
        try {
            while (x == 0.0 && y == 0.0) {
                long ws = sl.tryConvertToWriteLock(stamp);  //读锁转换为写锁
                if (ws != 0L) {
                    stamp = ws;
                    x = newX;
                    y = newY;
                    break;
                } else {
                    sl.unlockRead(stamp);
                    stamp = sl.writeLock();
                }
            }
        } finally {
            sl.unlock(stamp);
        }
    }
}

测试用例:

@Test
public void testStamped() throws InterruptedException {
    Point point = new Point();
    point.setX(1);
    point.setY(2);
    // 线程0 执行了乐观读
    Thread thread0 = new Thread(() -> {
        try {
            // 乐观读
            point.distanceFromOrigin();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "thread-0");
    thread0.start();

    Thread.sleep(500);
    // 线程1 执行写锁
    Thread thread1 = new Thread(() -> {
        // 乐观读
        try {
            point.move(3, 4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "thread-1");
    thread1.start();

    thread0.join();
    thread1.join();
}

结果:

性能对比

正是由于StampedLock的乐观读模式,早就StampedLock的高性能和高吞吐量,那么具体的性能提高有多少呢?

下图是和ReadWritLock相比,在一个线程情况下,读速度是其4倍左右,写是1倍。

下图是16个线程情况下,读性能是其几十倍,写性能也是近10倍左右:

下图是吞吐量提高:

那么这样是不是说StampedLock可以全方位的替代ReentrantReadWriteLock, 答案是否定的,StampedLock相对于ReentrantReadWriteLock有下面两个问题:

所以最终选择StampedLock还是ReentrantReadWriteLock,还是要看具体的业务场景。

总结

本文主要讲解了StampedLock的功能和使用,至于原理,StampedLock虽然不像其它锁一样定义了内部类来实现AQS框架,但是StampedLock的基本实现思路还是利用CLH队列进行线程的管理,通过同步状态值来表示锁的状态和类型,具体的源码实现大家感兴趣的自己可以追踪看看。

以上就是Java利用StampedLock实现读写锁的方法详解的详细内容,更多关于Java StampedLock读写锁的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯