什么是原子引用
带版本号 的原子操作!每次对值进行修改时,都会对比版本号,判断这个值是否被修改过。
如果没有修改则对其进行修改,如果修改过了,那么就会导致修改不成功。
查看官方文档中,原子引用的类
可以设置带版本的原子操作,一般常用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(); } }
都修改失败,所以在使用时需要注意!