首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解Java8并发工具类StampedLock

深入理解Java8并发工具类StampedLock

作者头像
我是攻城师
发布2018-09-30 11:33:00
5400
发布2018-09-30 11:33:00
举报
文章被收录于专栏:我是攻城师我是攻城师

StampedLock类是JDK8里面新增的一个并发工具类,这个类比较特殊,在此之前我们先简单的了解一下关于数据库或者存储系统的锁策略和机制。

总体上来说锁有两大类:

悲观锁:总是认为会有冲突发生,所以每次操作临界区资源时都会加锁。

乐观锁:顾名思义,认为每次操作临界区资源时不会发生冲突,但会先记录一个版本号,在提交事务时,会检查版本号是否变更,从而作出判断放弃或者重试。

对于一个高并发的应用程序来说,数据库常常会成为一个访问的瓶颈,这里面主要存在以下的几种访问情况:

(1)读读并发

(2)读写并发

(3)写写并发

一般情况下,数据库都会有读共享写独占的锁并发的方案,也就是说读读并发是没问题的,但在读写并发时,则有可能出现读取不一致情况,也就是常说的脏读,所以在悲观锁的模式下,在有写线程的时候,是不允许有任何其他的读和写线程的,也就是说写是独占的,这样会导致系统的吞吐明显下降,如何避免这一情况,于是就出现了基于MVCC多版本控制并发的策略,在这种策略下读写并发是可以同时进行的,底层的原理是当前有并发的写线程在独占,那么读线程就直接读取事务log里面的历史最新版本的数据,这样以来就大大提高了并发吞吐能力,虽然读取的数据并不是最新的数据,但是历史上最新的,同时也保持了一致性,目前主流的数据库都支持这种模式。最后一种是写写并发场景,这种场景通常基于乐观锁的并发写方案也称OCC,多个并发的写线程,每个线程都不会修改原始数据,而是从原始数据上拷贝上一份数据,同时记录版本号,不同的线程更新自己的数据,在最终写会时会判断版本号是否变更,如果变更则意味有人已经更改过了,那么当前线程需要做的就是自旋重试,如果重试指定的次数依然失败,那么就应该放弃更新,这种策略仅仅适合写并发并不强烈的场景,如果写竞争严重,那么多次自旋重试的开销也是非常耗性能的,如果竞争激烈,那么写锁独占的方式则更加适合。

基于上面谈到的这些内容,我们再来分析StampedLock类,就会非常比较容易理解,它实际主要解决的是读写并发场景更加类似于上面我们谈到的MVCC的模式。

StampedLock类有三种模式:

(一)写锁,这里的写锁是独占和排它的,这里对于申请写锁成功的线程会得到一个stamp,在释放锁unlockWrite(long)的时候会传入这个票据,申请写锁还支持非阻塞模式的调用通过tryWriteLock方法或者可超时的申请,处于写锁状态下,任何其他的写锁,读锁,乐观读锁都会失败。

(二)读锁,申请成功会返回一个票据,同理在释放的时候unlockRead(long)也需要传回票据。读锁是共享的,前提是没有任何写锁占用。

(三)乐观读锁,是新的特性,这种策略非常轻量级,在操作数据时候并没有使用CAS来设置锁的状态,如果当前没有线程持有写锁,那么乐观读锁就会立即返回一个非0的票据,在获得之后,为了保持一致性,要拷贝需要使用的相关数据到线程的的栈里面,然后再次判断票据是否有效,如果无效,则意味着这期间有线程修改了数据状态,所以这时候要么放弃操作,要么直接申请读锁,如果票据有效则意味着,当前的数据没有被更改过,可能不是最新的,但是一致的,在读写并发时候,用来读取是没有问题的,所以效率会高很多,因为没有使用任何的加锁操作,我们可以理解读取的数据类似MVCC的快照,最多不是最新的,但一定是一致的。

StampedLock类的主要特点,我认为有两个:

(1)通过乐观读锁支持读写并发,这里使用的是票据对比。

(2)支持读锁升级成写锁

下面我们看一下官网给出的例子,分别展示了写锁,乐观读锁和读锁升级成写锁的案例:

class Point {

    // 成员变量
    private double x, y;

    // 锁实例
    private final StampedLock sl = new StampedLock();

    // 排它锁-写锁(writeLock)
    void move(double deltaX, double deltaY) {
        long stamp = sl.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }

    // 乐观读锁(tryOptimisticRead)
    double distanceFromOrigin() {

        // 尝试获取乐观读锁(1)
        long stamp = sl.tryOptimisticRead();
        // 将全部变量拷贝到方法体栈内(2)
        double currentX = x, currentY = y;
        // 检查在(1)获取到读锁票据后,锁有没被其他写线程排它性抢占(3)
        if (!sl.validate(stamp)) {
            // 如果被抢占则获取一个共享读锁(悲观获取)(4)
            stamp = sl.readLock();
            try {
                // 将全部变量拷贝到方法体栈内(5)
                currentX = x;
                currentY = y;
            } finally {
                // 释放共享读锁(6)
                sl.unlockRead(stamp);
            }
        }
        // 返回计算结果(7)
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }

    // 读锁升级成写锁
    void moveIfAtOrigin(double newX, double newY) {
        // 这里可以使用乐观读锁替换(1)
        long stamp = sl.readLock();
        try {
            // 如果当前点在原点则移动(2)
            while (x == 0.0 && y == 0.0) {
                // 尝试将获取的读锁升级为写锁(3)
                long ws = sl.tryConvertToWriteLock(stamp);
                // 升级成功,则更新票据,并设置坐标值,然后退出循环(4)
                if (ws != 0L) {
                    stamp = ws;
                    x = newX;
                    y = newY;
                    break;
                } else {
                    // 读锁升级写锁失败则释放读锁,显示获取独占写锁,然后循环重试(5)
                    sl.unlockRead(stamp);
                    stamp = sl.writeLock();
                }
            }
        } finally {
            // 释放锁(6)
            sl.unlock(stamp);
        }
    }
}

总结:

本文主要介绍了JDK8里面新增的并发工具类StampedLock,相比ReentrantLock重入锁提供了更好的性能,并且支持读写并发的场景和读锁升级成写锁的功能,在使用时候一定注意乐观读锁需要先获取票据,然后在拷贝实例数据到线程栈,然后接着判断票据是否有效,如果位置搞反,那么则有可能使用出错,这一点需要注意。最后我们还要记住StampedLock是不支持重入的,尽管你可以通过锁转换来变相实现,还有对于StampedLock锁这里并没有明确强调公平和非公平的概念,这里StampedLock会尽量保证最好的性能。

https://docs.oracle.com/javase/8/docs/api/

https://www.jianshu.com/p/481071ddafd3

https://netjs.blogspot.com/2016/08/stampedlock-in-java.html

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 我是攻城师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档