一个带有邮戳的锁StampedLock(jdk1.8出现)

简介: jdk1.8真的可以作为一个宝藏,随便一个新的特性都足以写一本书,今天分析一个在jdk1.8中,引入的一个新的带有邮戳的StampedLock。这篇文章主要从使用的角度来分析一下:

一、为什么会需要StampedLock?


任何一个新引入的知识都是为了解决以往系统中出现的问题,否则新引入的将变得毫无价值。我曾经写过一些关于ReentrantReadWriteLock, ReentrantLock 和synchronized锁的文章。如果你之前了解过这些锁或者在工作中使用过,你会发现他们都有各种各样的缺点。


比如synchronized不可中断等,ReentrantLock 未能读写分离实现,虽然ReentrantReadWriteLock能够读写分离了,但是对于其写锁想要获取的话,就必须没有任何其他读写锁存在才可以,这实现了悲观读取。而且如果读操作很多,写很少的情况下,线程有可能遭遇饥饿问题。


饥饿问题:ReentrantReadWriteLock实现了读写分离,想要获取读锁就必须确保当前没有其他任何读写锁了,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,因为当前有可能会一直存在读锁。而无法获得写锁。

这时候怎么办呢?于是在jdk1.8的时候引入了一个新的锁StampedLock。


二、简单使用


StampedLock控制锁有三种模式(写,读,乐观读)


(1)写入(Writing):writeLock是一个独占锁,也是一个悲观锁。

(2)读取(Reading):readLock这时候是一个悲观锁。

(3)乐观读取(Optimistic Reading):提供了tryOptimisticRead方法返回一个非0的stamp,只有当前同步状态没有被写模式所占有是才能获取到。乐观读取模式仅用于短时间读取操作时经常能够降低竞争和提高吞吐量。同时使用的时候一般需要读取并存储到另外一个副本,以用做对比使用。 下面干脆使用代码来实现一下这几种锁的实现。


1、写锁的实现:悲观写


public class StampedTest {
    private static StampedLock lock = new StampedLock();
    private static List<String> data = new ArrayList<String>();
    public static void write() {
        long stamped = -1;
        try {
            stamped = lock.writeLock();
            data.add("写线程写入的数据"+stamped);
            System.out.println("写入的数据是:" + stamped);
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlockWrite(stamped);
        }
    }
}

悲观写的意思是,认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改。


2、读锁的实现:悲观读


public class StampedTest {
    private static StampedLock lock = new StampedLock();
    private static List<String> data = new ArrayList<String>();
    public static void read() {
        long stamped = -1;
        try {
            stamped = lock.readLock();
            for (String name : data) {
                System.out.println("读的数据是:" + name);
            }
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlockRead(stamped);
        }
    }
}

3、测试

public class StampedTest {
    private static StampedLock lock = new StampedLock();
    private static List<String> data = new ArrayList<String>();
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        Runnable readTask = () -> {
            for (;;) {
                read();
            }
        };
        Runnable writeTask = () -> {
            for (;;) {
                write();
            }
        };
        for(int i=0;i<9;i++) {
            executor.submit(readTask);
        }
        executor.submit(writeTask);
    }
}

注意到,这三块都是在同一个类中,为了演示方便就这样分开描述了。在运行测试的时候我们会发现,读的线程比较多,但是写的线程比较少,因此读锁执行的概率比较大一些。整个流程是先写数据,然后再读数据,在读数据的时候不会执行写操作。我们来实现一下第三种,那就乐观读取。


4、读锁的实现:乐观读


public class StampedTest {
    private static StampedLock lock = new StampedLock();
    private static List<String> data = new ArrayList<String>();
    public static void optimisticRead() {
        //尝试去拿一个乐观锁
        long stamped = lock.tryOptimisticRead();
        //如果没有线程修改,我们再去获取一个读锁
        if(lock.validate(stamped)) {
            try {
                stamped = lock.readLock();
                for (String name : data) {
                    System.out.println("读的数据是:" + name);
                }
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlockRead(stamped);
            }
        }
    }
}

这时候我们定义了一个新的乐观读锁,意思是在读的时候,依然可以写入。再次运行的时候你会发现这次写操作会比之前的测试次数多了,这说明在使用乐观读的时候,也发生了写操作。


三、总结


StampedLock的调度策略对待读写操作都是公平合理的。所有try方法都是尽最大努力,调用可能会成功,也可能会失败。这个类没有直接实现Lock或者ReadWriteLock方法,源码中是把他当做一个单独的类来实现的。当然,一个StampedLock可以通过asReadLock,asWriteLock,asReadWriteLock方法来得到全部功能的子集。

相关文章
|
4月前
|
Oracle Java 关系型数据库
【颠覆性升级】JDK 22:超级构造器与区域锁,重塑Java编程的两大基石!
【9月更文挑战第6天】JDK 22的发布标志着Java编程语言在性能和灵活性方面迈出了重要的一步。超级构造器和区域锁这两大基石的引入,不仅简化了代码设计,提高了开发效率,还优化了垃圾收集器的性能,降低了应用延迟。这些改进不仅展示了Oracle在Java生态系统中的持续改进和创新精神,也为广大Java开发者提供了更多的可能性和便利。我们有理由相信,在未来的Java编程中,这些新特性将发挥越来越重要的作用,推动Java技术不断向前发展。
解决线程安全问题的方式三:Lock锁 ---JDK5.0新增
解决线程安全问题的方式三:Lock锁 ---JDK5.0新增
41 0
详解JDK锁02:万字文!结合实战案例,手撕AQS源码!
详解JDK锁02:万字文!结合实战案例,手撕AQS源码!
113 0
|
Java C++
详解JDK锁01:结合源码一文弄懂Lock接口!
详解JDK锁01:结合源码一文弄懂Lock接口!
136 0
|
存储 安全 算法
面试官问:JDK8 的ConcurrentHashMap为什么放弃了分段锁
我是鸭血粉丝,今天我们来讨论一下一个比较经典的面试题就是 ConcurrentHashMap 为什么放弃使用了分段锁,这个面试题阿粉相信很多人肯定觉得有点头疼,因为很少有人在开发中去研究这块的内容,今天阿粉就来给大家讲一下这个 ConcurrentHashMap 为什么在 JDK8 中放弃了使用分段锁。
1048 0
面试官问:JDK8 的ConcurrentHashMap为什么放弃了分段锁
|
Java
JDK5中Lock锁的使用
(1)JDK5中Lock锁的使用   虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock接口。
966 0
|
Java
从JDK源码角度看并发锁的优化
        在CLH锁核心思想的影响下,JDK并发包以CLH锁作为基础而设计,其中主要是考虑到CLH锁更容易实现取消与超时功能。比起原来的CLH锁已经做了很大的改造,主要从两方面进行了改造:节点的结构与节点等待机制。
1136 0
|
4月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
396 3
|
10天前
|
NoSQL 关系型数据库 MySQL
Linux安装jdk、mysql、redis
Linux安装jdk、mysql、redis
101 7