From 8ed537cdba6cb25dcdb9d847c0ab12d7b7267de3 Mon Sep 17 00:00:00 2001 From: whl <313576743@qq.com> Date: Thu, 19 Dec 2019 12:49:56 +0800 Subject: [PATCH 1/3] commit --- week_02/25/AtomicInteger_25.md | 111 ++++++++++++++++++ week_02/25/Unsafe_25.md | 207 +++++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+) create mode 100644 week_02/25/AtomicInteger_25.md create mode 100644 week_02/25/Unsafe_25.md diff --git a/week_02/25/AtomicInteger_25.md b/week_02/25/AtomicInteger_25.md new file mode 100644 index 0000000..6f6b39f --- /dev/null +++ b/week_02/25/AtomicInteger_25.md @@ -0,0 +1,111 @@ +# AtomicInteger +AtomicInteger位于j.u.c.atomic包下,其操作都具备原子性,主要利用了CAS + volatile保证原子操作,从而避免synchronized的高开销,执行效率大大提升。CAS的实现主要通过了Unsafe下的compareAndSwapInt()方法进行了实现,该方法是一个native方法,通过比较源对象地址与更新时预期的源对象地址是否相同来决定是否执行更新操作。 + +### 主要属性 +```java + //获取Unsafe实例对象, 目的是使用Unsafe.compareAndSwapInt进行update操作 + private static final Unsafe unsafe = Unsafe.getUnsafe(); + //标识value字段的偏移量 + private static final long valueOffset; + + //初始化: 通过unsafe获取value的偏移量 + static { + try { + //获取value字段偏移量 + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + //存储int类型值的位置, 采用volatile修饰 + //保证JVM总能获取到该变量的最新值 + private volatile int value; +``` + +### compareAndSet() +调用Unsafe.compareAndSwapInt()实现CAS操作,保证只有当对应偏移量处的字段是期望值时才会执行更新操作,这样就避免出现当多个线程同时读取value时,某个线程读取到value = a,而在准备更新时value却被其他线程更新为b的情况。 +```java + //调用unsafe.compareAndSwapInt()实现, 在Unsafe下通过native方法实现CAS操作 + //只有当字段偏移量valueOffset = 期望值expect时才进行更新 + public final boolean compareAndSet(int expect, int update) { + //参数分别是:操作的对象, 对象中字段的偏移量, 期望值, 修改后的值 + return unsafe.compareAndSwapInt(this, valueOffset, expect, update); + } +``` + +### getAndIncrement() +获取到当前的值并自增,底层用到了CAS + 自旋的乐观锁机制,其他的原子自减getAndDecrement()方法,亦或是原子更新getAndUpdate()方法也是同样的实现方式。 +```java + public final int getAndIncrement() { + //底层调用unsafe.getAndAddInt()方法 + //参数分别是: 操作对象, 对象中字段的偏移量, 要增加的值 + return unsafe.getAndAddInt(this, valueOffset, 1); + } + + //Unsafe.getAndAddInt + //获取当前的值并增加指定值 + public final int getAndAddInt(Object var1, long var2, int var4) { + int var5; + do { + //获取当前的值的 + var5 = this.getIntVolatile(var1, var2); + //不断循环尝试更新对应偏移量位置的值, 直到成功为止 + //经典CAS + 自旋锁 + } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); + return var5; + } +``` + +### AtomicInteger对比synchronized +通过同时启100个线程,每个线程自增10w次,在保证线程安全的情况下,我们使用两种不同的方式对其进行实现,并对比它们的执行时间: +1. synchronized:663ms +```java + public static int count = 0; + + public static synchronized void increment() { + count++; + } + + public static void main(String[] args) { + long a = System.currentTimeMillis(); + for (int i = 0; i < 100; i++) { + new Thread(()->{ + for (int j = 0; j < 100000; j++) { + increment(); + } + }).start(); + } + while (Thread.activeCount() > 2) { + Thread.yield(); + } + System.out.println(count); + System.out.println(System.currentTimeMillis() - a); + } +``` +2. AtomicInteger:296ms +```java + public static AtomicInteger count = new AtomicInteger(); + + public static void increment() { + count.incrementAndGet(); + } + + public static void main(String[] args) { + long a = System.currentTimeMillis(); + for (int i = 0; i < 100; i++) { + new Thread(()->{ + for (int j = 0; j < 100000; j++) { + increment(); + } + }).start(); + } + while (Thread.activeCount() > 2) { + Thread.yield(); + } + System.out.println(count); + System.out.println(System.currentTimeMillis() - a); + } +``` +可以看到同样的线程安全自增,AtomicInteger这种CAS + 自旋的乐观锁实现比起synchronized同步块的悲观锁实现要快上两倍左右,这也是为什么我们要使用AtomicInteger。 + +### ABA问题 +AtomicInteger也并非十全十美,因为它可能会出现ABA问题,对于ABA问题我们可以想象:你有一瓶洗发水,剩余容量500ml,你的舍友某天用了你的洗发水100ml,但怕被你发现,于是舍友在用完后又偷偷加了100ml的水进去使洗发水看上去还是500ml,但洗发水确实已经被用过了。对于ABA问题的解决访问可以参考AtomicStampedReference。 \ No newline at end of file diff --git a/week_02/25/Unsafe_25.md b/week_02/25/Unsafe_25.md new file mode 100644 index 0000000..e9e0f63 --- /dev/null +++ b/week_02/25/Unsafe_25.md @@ -0,0 +1,207 @@ +# Unsafe +Unsafe为我们提供了访问底层的机制,这种机制仅供Java核心类库使用,该特点在获取Unsafe的实例对象上可以很明显地看出,但是我们依旧可以通过反射获取Unsafe实例并使用它。 + +### 1. 获取Unsafe实例 +```java + private static final Unsafe theUnsafe; + + //经典单例模式 + //构造器私有化, 通过静态方法返回同一个对象 + private Unsafe() { + } + + @CallerSensitive + public static Unsafe getUnsafe() { + //获取调用Unsafe的Class对象 + Class var0 = Reflection.getCallerClass(); + //若Class对应的类加载器采用的不是BootStrapClassLoader, 证明其并非核心类, 那么就抛出异常 + if (!VM.isSystemDomainLoader(var0.getClassLoader())) { + throw new SecurityException("Unsafe"); + } else { + return theUnsafe; + } + } +``` +Unsafe提供了一个静态方法getUnsafe()用于获取它的实例对象,但直接调用会抛出SecurityException异常,这是因为Unsafe只提供给Java核心类使用,那么可以通过反射获取它的实例: +```java + public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { + // System.out.println(Class.class.getClassLoader()); //null --> BootStrapClassLoader + Field field = Unsafe.class.getDeclaredField("theUnsafe");//getField只能获取public属性 + field.setAccessible(true);//关闭访问安全检查开关 + Unsafe unsafe = (Unsafe) field.get(null); + } +``` + +### 2. 使用Unsafe实例化一个类 +```java + //Unsafe通过一个native方法实现实例化一个类, 但是只会分配内存, 并不会调用该类的构造方法 + public native Object allocateInstance(Class var1) throws InstantiationException; +``` + +### 3. 修改任意私有字段的值 +我们可以通过Unsafe.putInt()修改User的私有int属性age的值,如果是其它类型的属性也可以调用对应的put方法进行修改,其底层都是通过native方法获取对象的地址 + 偏移值实现update。 +```java +public class UnsafeTest { + public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { + //获取unsafe实例 + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + Unsafe unsafe = (Unsafe) f.get(null); + //获取user实例, 并指定age = 19 + User user = new User(19); + System.out.println(user.getAge());//19 + //通过反射获取User.class的age属性 + Field age = user.getClass().getDeclaredField("age"); + //调用unsafe.putInt, 参数为: 指定对象, 偏移量, 修改后的值 + //putInt方法通过获取user对象的起始地址, 加上objectFieldOffset()获取到的偏移值得到对应的update地址, 然后将20写入对应的update地址 + unsafe.putInt(user, unsafe.objectFieldOffset(age), 20); + System.out.println(user.getAge());//20 + } +} + +class User { + private int age; + + public User(int age) { + this.age = age; + } + + public int getAge() { + return age; + } +} +``` +这部分也可以说体现了Unsafe不安全的特点,因为Unsafe是通过一个native方法直接对内存进行读写操作实现了更新: +```java + //通过传入更新对象var1, 偏移值var2, 更新的值var3完成更新 + public native void putInt(Object var1, long var2, int var4); + + //调用native方法, 获取Field属性的偏移值 + public native long objectFieldOffset(Field var1); +``` +不仅如此,我们也可以直接通过反射进行私有属性值的修改,如下所示: +```java +public class UnsafeTest { + public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + Unsafe unsafe = (Unsafe) f.get(null); + + User user = new User(19); + System.out.println(user.getAge()); + + Field age = user.getClass().getDeclaredField("age"); + age.setAccessible(true); + age.set(user, 20); + System.out.println(user.getAge()); + } +} + +class User { + ... +} +``` + +### 4. 抛出checked异常 +对于checkedException一般我们都需要在编译器对其进行显示地捕获,可以通过try-catch对其进行捕获,或者在方法上通过throws抛出这个异常交给上层处理。而通过Unsafe,我们可以直接抛出一个checked异常,同时却不用捕获或在方法签名上定义它。 +```java +private static Unsafe unsafe; + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + } + + public static void readFile() throws IOException { + throw new IOException(); + } + + public static void readFileUnsafe() { + unsafe.throwException(new IOException()); + } +``` + +### 5. 使用堆外内存 +若进程在运行过程中JVM内存不足就会导致频繁的进行GC,理想情况下可以考虑通过Unsafe.allocateMemory()申请使用堆外内存,这块堆外内存由于不受JVM管理,因此必须通过freeMemory()方法手动释放它。 + +下面是一个基于Unsafe实现的堆外数组,(我还是第一次知道数组能够手写出来...) +```java +public class OffHeapArray { + private static final int INT = 4;//一个int位占用的字节数 + private long size; + private long addr; + + private static Unsafe unsafe; + + static { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + unsafe = (Unsafe) f.get(null); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + //指定堆外数组的大小, 并通过unsafe.allocateMemory()分配内存 + public OffHeapArray(long size) { + this.size = size; + addr = unsafe.allocateMemory(size * INT);//内存大小 = 数组长度 * 32bit(4字节) + } + + //获取指定索引处的元素 + public int get(long index) { + //通过unsafe.getInt方法获取到指定内存地址上的值 + return unsafe.getInt(addr + index * INT); + } + + //设置指定索引处的元素 + public void set(long index, int value) { + unsafe.putInt(addr + index * INT, value); + } + + //数组长度 + public long getSize() { + return size; + } + + //释放堆外内存 + public void freeMem() { + unsafe.freeMemory(addr); + } +} +``` + +### 6. CompareAndSwap(CAS) +Juc包下大量使用了CAS操作,其底层都是通过Unsafe的CompareAndSwap操作实现的,这种方式广泛运用于无锁算法,与Java中标准的悲观锁机制相比,CAS的效率更加高效。 + +##### 关于CAS的思想这里稍微提一下: +有三个参数:当前内存值object、更新时预期的内存值expect、即将更新的值update,当且仅当expect和value相同时,才将object修改为update并返回true,否则什么都不做并返回false。 + +这样配合volatile关键字就能够避免在多线程环境下,多个线程同时读取同一个资源并修改造成的线程不安全问题。试想一下,线程A读取并修改了资源stock,但还未回写到主内存,此时线程B通过volatile发现线程A修改了资源stock,那么更新时的预期内存值expect就与当前内存值object不相同,也就不会进行更新操作。 + +下面是AtomicInteger实现的核心方法——compareAndSwapInt() +```java + /** + //compareAndSwapInt的实现逻辑可以用如下代码所示 + if (object == expect) { + object = update; + return true; + } else { + return false; + } + */ + public final native boolean compareAndSwapInt(Object object, long valueOffset, int expect, int update); +``` + +### 7. 阻塞或唤醒线程 +JVM在上下文切换时调用了Unsafe.park()和Unsafe.unpark(),当一个线程用完了分配给它的CPU时间片,则JVM会调用Unsafe.park()方法来阻塞此线程;当一个被阻塞的线程需要再次运行时,JVM调用Unsafe.unpark()方法来唤醒此线程。 \ No newline at end of file -- Gitee From 4757a29fe70ff4a2cc633b9415419f7ab9d6d7aa Mon Sep 17 00:00:00 2001 From: whl <313576743@qq.com> Date: Fri, 20 Dec 2019 11:00:15 +0800 Subject: [PATCH 2/3] commit --- week_01/25/LinkedList_25.md | 257 ++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 week_01/25/LinkedList_25.md diff --git a/week_01/25/LinkedList_25.md b/week_01/25/LinkedList_25.md new file mode 100644 index 0000000..82f9eea --- /dev/null +++ b/week_01/25/LinkedList_25.md @@ -0,0 +1,257 @@ +# LinkedList +LinkedList采用双端链表的数据结构实现,且由于双端链表的特性,LinkedList也可以作为队列、双端队列来使用,不仅如此也可以作为栈使用。 + +```java +public class LinkedList extends AbstractSequentialList implements List, Deque, Cloneable, Serializable { +``` +(1)实现了List接口,具备List的特性 +(2)实现Deque接口,具备双端链表特性 +(3)实现Clonable接口,可以被克隆 +(4)实现了Serializable,可以被序列化 + +### 属性 +```java + //元素个数 + transient int size; + //头结点 + transient LinkedList.Node first; + //尾结点 + transient LinkedList.Node last; + + //双链表节点内部类 + private static class Node { + E item;//数据 + Node next;//next指针 + Node prev;//prev指针 + + //初始化构造方法 + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } + } +``` + +### 构造方法 +```java + /** + * 1. 默认构造 + */ + public LinkedList() { + } + + /** + * 2. 通过传入一个集合创建链表 + */ + public LinkedList(Collection c) { + this(); + addAll(c); + } +``` + +### 头部或末尾添加元素 +作为双端链表,可以在链表头部添加节点,也可以在链表尾部添加节点,其时间复杂度都是O(1) ,而作为双端队列时所用到的offerFirst,offerLast都是调用了下面的方法。 +```java + /** + * 1. 添加元素到链表头部 + */ + private void linkFirst(E e) { + //头结点 + final Node f = first; + //创建newNode.value = e, newNode.next-> 头结点 + final Node newNode = new Node<>(null, e, f); + //将newNode置为新的头结点 + first = newNode; + //判断是否是第一个添加的节点 + if (f == null) + //如果是, 则把newNode也置为尾节点 + last = newNode; + else + //否则原来的头节点.prev指向newNode + f.prev = newNode; + size++; + modCount++; + } + + /** + * 2. 链表尾部添加元素 + */ + void linkLast(E e) { + //获取末尾节点 + final Node l = last; + //创建newNode.value = e, newNode.prev = 尾结点 + final Node newNode = new Node<>(l, e, null); + //将newNode置为新的尾结点 + last = newNode; + //若第一次添加节点 + if (l == null) + //则把newNode也置为first节点 + first = newNode; + else + //否则原尾结点.next指向newNode + l.next = newNode; + size++; + modCount++; + } +``` + +### add(int index, E element) index位置添加元素 +由于node(int index)涉及到链表的遍历,因此add(int i, E e)方法时间复杂度是O(n) +```java + public void add(int index, E element) { + //判断是否越界 + checkPositionIndex(index); + //若插入的节点是末尾节点 + if (index == size) + //直接调用linkLast + linkLast(element); + else + //否则先通过index检索到插入节点的后置节点 + //再调用linkBefore进行插入 + linkBefore(element, node(index)); + } + + /** + * 通过index检索到目标节点 + */ + Node node(int index) { + //若index < size/2, 则从头节点开始遍历 + if (index < (size >> 1)) { + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } else { + //若index >= size/2, 则从尾结点开始遍历 + Node x = last; + for (int i = size - 1; i > index; i--) + x = x.prev; + return x; + } + } + + /** + * 在某个不为null的节点前插入一个新节点 + */ + void linkBefore(E e, Node succ) { + // 找到待插入节点的前置节点pred + final Node pred = succ.prev; + // 创建newNode.prev = 前置节点, newNode.next = 后置节点succ + final Node newNode = new Node<>(pred, e, succ); + // 后置节点succ.prev = newNode + succ.prev = newNode; + // 若前置节点为空, 说明succ原本是头节点 + if (pred == null) + //此时将newNode置为新的头节点 + first = newNode; + else + //否则前序节点pred.next = newNode + pred.next = newNode; + size++; + modCount++; + } +``` + +### 头部或末尾删除元素 +作为双端链表,可以在链表头部删除节点,也可以在链表尾部删除节点,其时间复杂度都是O(1) ,而作为双端队列时所用到的pollFirst,pollLast都是调用了下面的方法。 + +```java + /** + * 删除链表头节点 + */ + private E unlinkFirst(Node f) { + // 获取当前头节点value + final E element = f.item; + // 获取头节点的后置节点next + final Node next = f.next; + f.item = null; + f.next = null; // help GC + // 后置节点next置为新的头节点 + first = next; + // 若删除了原头节点链表为空, 证明原头节点也末尾节点 + if (next == null) + //那么把末尾节点也置为空 + last = null; + else + //否则后置节点.prev置为空 + next.prev = null; + size--; + modCount++; + //返回删除节点value + return element; + } + + /** + * 删除末尾节点 + */ + private E unlinkLast(Node l) { + // 获取当前尾节点value + final E element = l.item; + // 获取尾节点的前置节点prev + final Node prev = l.prev; + l.item = null; + l.prev = null; // help GC + // prev置为新的尾结点 + last = prev; + // 如果prev == null, 说明原尾节点也是头节点 + if (prev == null) + //头节点置为空 + first = null; + else + //否则prev.next置为空 + prev.next = null; + size--; + modCount++; + return element; + } +``` + +### remove(int index) index位置删除元素 +```java + public E remove(int index) { + checkElementIndex(index); + //同样的, 先调用node(int index)检索到指定节点 + return unlink(node(index)); + } + + /** + * 删除指定节点x + */ + E unlink(Node x) { + // x的元素值 + final E element = x.item; + // x的后置 + final Node next = x.next; + // x的前置 + final Node prev = x.prev; + // 如果前置节点为空, 说明x是头节点 + if (prev == null) { + //将头节点置为后置节点next + first = next; + } else {// 否则修改前置节点prev.next = x的后置 + prev.next = next; + x.prev = null;//help GC + } + + //如果后置节点为空, 说明x是尾结点 + if (next == null) { + //将前置prev置为新的尾节点 + last = prev; + } else {//否则修改后置节点next.prev = x的前置 + next.prev = prev; + x.next = null;//help GC + } + + x.item = null;// help GC + size--; + modCount++; + return element; + } +``` + +### 总结 +(1)LinkedList是以双端链表实现的List +(2)同时具备双端队列,队列,栈的特性 +(3)首尾增、删元素非常高效,时间复杂度为O(1) +(4)中间增、删元素时间复杂度为O(n) \ No newline at end of file -- Gitee From 0a04f814f8105e0f53b70a9900fd1f201a165031 Mon Sep 17 00:00:00 2001 From: whl <313576743@qq.com> Date: Sun, 22 Dec 2019 15:58:07 +0800 Subject: [PATCH 3/3] commit --- week_02/25/AtomicStampReference.md | 116 +++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 week_02/25/AtomicStampReference.md diff --git a/week_02/25/AtomicStampReference.md b/week_02/25/AtomicStampReference.md new file mode 100644 index 0000000..485dbde --- /dev/null +++ b/week_02/25/AtomicStampReference.md @@ -0,0 +1,116 @@ +# AtomicStampedReference +AtomicStampedReference是j.u.c.atomic包下提供的一个原子类,它能通过维护一个版本号解决其它原子类无法解决的ABA问题。 + +### ABA问题 +我们知道CAS操作底层是依赖于Unsafe.compareAndSwap操作实现的,它通过比较源内存地址与更新时期望的内存地址是否相同来决定是否执行更新操作,但如果某线程1在读取了内存地址X = A的同时,线程2读取并修改了内存地址X的地址 = B之后又再次读取并修改回内存地址X = A,这时线程1就会认为内存地址X是没有没修改过的。 + +用代码表示ABA过程如下所示: +```java +public class ABATest { + public static void main(String[] args) { + AtomicInteger at = new AtomicInteger(1); + + //线程1先读取到value, 然后阻塞1s + new Thread(() -> { + int value = at.get(); + System.out.println("thread1 read value = " + value); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (at.compareAndSet(value, 3)) {//if update success return true + System.out.println("thread1 update from " + value + " to 3"); + } else { + System.out.println("thread1 update fail"); + } + }).start(); + + //在线程1阻塞的同时, 线程2对value进行了从1->2->1的更新 + new Thread(() -> { + int value = at.get(); + System.out.println("thread2 read value = " + value); + + if (at.compareAndSet(value, 2)) { + System.out.println("firstly, thread2 update from " + value + " to 2"); + value = at.get(); + System.out.println("thread2 read value after update " + value); + if (at.compareAndSet(value, 1)) { + System.out.println("Secondly, thread2 update from " + value + " to 1"); + } + } + }).start(); + } +} +``` +执行结果: +``` +thread1 read value = 1 +thread2 read value = 1 +thread2 update once from 1 to 2 +thread2 read value after update once = 2 +thread2 update second from 2 to 1 +thread1 update from 1 to 3 +``` +看上去好像没什么问题,但假设我们有一个无锁的栈结构如下:[1, 2, 3, 4, 5],线程A读取了栈顶元素1,但还未执行pop时就被阻塞了,与此同时线程B执行了两次pop,并push(1)到栈顶,此时栈结构如下:[1, 3, 4, 5],线程A通过CAS操作比较之后继续执行pop,然而结果本该是[2, 3, 4, 5],此时却因为ABA问题变成了[3, 4, 5],这就是ABA问题的危害。 + +### ABA问题解决 +1. 我们可以通过在上述栈结构中增加一个版本号用于控制,每执行一次更新操作,版本号 + 1,且执行更新操作时,检查元素值的同时也检查版本号是否一致,这样就能够保证CAS的安全。 +2. 不仅如此,在上述线程B执行push(1)操作时,创建一个新的引用地址value传入,而不是复用之前头元素1的引用地址。 + +同样的AtomicStampedReference采用了将元素值与版本号绑定的方式,并在更新操作时,创建新的引用地址进行更新,实现了ABA问题的解决。下面是AtomicStampedReference的源码分析: + +### 内部类 +实现将元素值与版本号进行绑定 +```java + private static class Pair { + final T reference;//元素值 + final int stamp;//邮戳(版本号) + private Pair(T reference, int stamp) { + this.reference = reference; + this.stamp = stamp; + } + static Pair of(T reference, int stamp) { + return new Pair(reference, stamp); + } + } +``` + +### 属性 +```java + //volatile保证Pair的修改对所有线程可见 + private volatile Pair pair; + //unsafe实例 + private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); + //获取pair的偏移量 + private static final long pairOffset = + objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); +``` + +### compareAndSet() +```java + public boolean compareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) { + //获取当前(元素值 + 版本号)对象 + Pair current = pair; + return + //若元素地址没变 + expectedReference == current.reference && + //版本号没变 + expectedStamp == current.stamp && + //新的元素地址 = 旧的元素地址 + ((newReference == current.reference && + //新的版本号 = 旧的版本号 + newStamp == current.stamp) || + //根据新的元素地址和版本号构造新的Pair并CAS更新 + casPair(current, Pair.of(newReference, newStamp))); + } +``` + +### 总结 +(1)在多线程环境下,使用无锁结构进行CAS操作时要注意ABA问题。 +(2)AtomicStampedReference通过版本号控制,并且每次添加元素时都会新建一个节点执行update操作实现了ABA问题的解决。 \ No newline at end of file -- Gitee