• 17、原子引用(乐观锁)


    什么是原子引用

    解决ABA 问题,引入原子引用! 对应的思想:乐观锁!

    带版本号 的原子操作!每次对值进行修改时,都会对比版本号,判断这个值是否被修改过。

    如果没有修改则对其进行修改,如果修改过了,那么就会导致修改不成功。

     

    判断值是否有修改过

    查看官方文档中,原子引用的类

     可以设置带版本的原子操作,一般常用AtomicStampedReference

    具体代码

    //AtomicStampedReference:和乐观锁的性质相同,可以记录每一次的修改,并且通过版本判断是否被修改过
    import java.io.UnsupportedEncodingException;
    import java.text.ParseException;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    public class MyTest {
        public static void main(String[] args) throws UnsupportedEncodingException, ParseException, ClassNotFoundException, ExecutionException, InterruptedException {
            // 创建原子引用对象,设定初始值为1, 版本号为1
            // 在真实业务中,一般使用具体的对象比如:User
            AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1, 1);
    
            // 捣乱线程
            new Thread(()->{
                int stamp = reference.getStamp();   // 获取版本号
                System.out.println(Thread.currentThread().getName() + "=> 获取到的版本号:" + stamp);
                try {
                    TimeUnit.SECONDS.sleep(2);  // 睡2秒,保证两个线程的版本号都输出一下,并且当前线程先执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 判断原子引用的版本号,和当前版本号是否相同,如果相同修改值,并对版本号+1,修改成功返回true
                boolean result1 = reference.compareAndSet(1, 2, reference.getStamp(), reference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "=> 将值从1修改为2,修改结果:" + result1 + ",当前值:" + reference.getReference() + ",当前版本号:" + reference.getStamp());
    
                // 判断原子引用的版本号,和当前版本号是否相同,如果相同修改值,并对版本号+1,修改成功返回true
                boolean result2 = reference.compareAndSet(2, 1, reference.getStamp(), reference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "=> 将值从2修改为1,修改结果:" + result2 + ",当前值:" + reference.getReference() + ",当前版本号:" + reference.getStamp());
            }, "B").start();
    
            // 正常修改线程
            new Thread(()->{
                int stamp = reference.getStamp();   // 版本号
                System.out.println(Thread.currentThread().getName() + "=> 获取到的版本号:" + stamp);
                try {
                    TimeUnit.SECONDS.sleep(3);  // 这里睡3秒,将获取到的版本号输出,并且保证捣乱的线程先执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 判断原子引用的版本号,和当前版本号是否相同,如果相同修改值,并对版本号+1,修改成功返回true
                boolean result = reference.compareAndSet(1, 3, stamp, stamp + 1);
                System.out.println(Thread.currentThread().getName() + "=> 将值从1修改为3,修改结果:" + result + ",当前值:" + reference.getReference() + ",当前版本号:" + reference.getStamp());
            }, "A").start();
    
        }
    }

    注意

    Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实 例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;

    // 下面是AtomicStampedReference这个类的源码
    private static class Pair<T> {
        final T reference;    
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&    // 这两个都是对象,所以==比较的是地址,如果地址不一样直接返回false
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

    再次测试

    • 将值设置为2020,查看是否可以修改
    import java.io.UnsupportedEncodingException;
    import java.text.ParseException;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    public class MyTest {
        public static void main(String[] args) throws UnsupportedEncodingException, ParseException, ClassNotFoundException, ExecutionException, InterruptedException {
            // 这里使用Integer对象,如果值是-128~127之间,那么比较的时候比较的是值
            // 如果不在这个区间,那么就会在堆内存中创建对象,那么比较的时候比的就是地址了
            AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(2020, 1);
    
            // 捣乱线程
            new Thread(()->{
                int stamp = reference.getStamp();   // 获取版本号
                System.out.println(Thread.currentThread().getName() + "=> 获取到的版本号:" + stamp);
                try {
                    TimeUnit.SECONDS.sleep(2);  // 睡2秒,保证两个线程的版本号都输出一下,并且当前线程先执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 这里将值从2020改为2021,但是传入的2020实际上是创建了一个对象new Integer(2020),reference这个对象中的2020在堆内存中是另一个对象
                // 虽然值相同,但是地址不同,所以修改不成功
                boolean result1 = reference.compareAndSet(2020, 2021, reference.getStamp(), reference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "=> 将值从2020修改为2021,修改结果:" + result1 + ",当前值:" + reference.getReference() + ",当前版本号:" + reference.getStamp());
    
                boolean result2 = reference.compareAndSet(2021, 2020, reference.getStamp(), reference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "=> 将值从2021修改为2020,修改结果:" + result2 + ",当前值:" + reference.getReference() + ",当前版本号:" + reference.getStamp());
            }, "B").start();
    
            // 正常修改线程
            new Thread(()->{
                int stamp = reference.getStamp();   // 版本号
                System.out.println(Thread.currentThread().getName() + "=> 获取到的版本号:" + stamp);
                try {
                    TimeUnit.SECONDS.sleep(3);  // 这里睡3秒,将获取到的版本号输出,并且保证捣乱的线程先执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                boolean result = reference.compareAndSet(2020, 2022, stamp, stamp + 1);
                System.out.println(Thread.currentThread().getName() + "=> 将值从2020修改为2022,修改结果:" + result + ",当前值:" + reference.getReference() + ",当前版本号:" + reference.getStamp());
            }, "A").start();
    
        }
    }

     都修改失败,所以在使用时需要注意!

    致力于记录学习过程中的笔记,希望大家有所帮助(*^▽^*)!
  • 相关阅读:
    利用UncaughtExceptionHandler捕获未try...catch到的异常
    nodejs
    angularjs异步处理 $q.defer()
    springboot集成swagger
    面试相关
    springboot注解
    关于自动拆装箱
    sonar集成搭建
    Predicate 类
    idea快捷键
  • 原文地址:https://www.cnblogs.com/zxhbk/p/13029685.html
Copyright © 2020-2023  润新知