赞
踩
本文乃个人拙见,如有错误,欢迎指出,不能误导新人
上一节讲到了CAS,最后说到了CAS的缺点,如果有很多写操作的话,CAS的性能会降低,这个时候还是使用synchronized比较合适。但是CAS还存在一个问题就是ABA问题。
之前说到,compareAndSwap,是比较持有值与内存值是否相等,然后进行插入,乍一看没什么毛病,如果A、B两个线程同时取到了主内存中的i=1,这个时候B线程操作比较快,将i写成2,然后写入主内存中,然后这个时候B线程很快速的又修改了一次,将2修改为了1,这个时候A线程调用CAS,发现内存中是1没问题,进行修改,这个时候其实中间就有了猫腻,如果是在栈中修改值的话,t1和t2同时对A进行操作,这个时候t2将栈中元素从A修改为了ABA,而t1线程比较A的时候发现两个值是相等的于是进行操作,这个时候是出问题的。
在JUC中的atomic包中有一个AtomicStampeReference类,这个类在每一次修改值的时候加一个版本号(stamp),就好比svn一样,每一次加一,这样要是t2线程进行修改两次的话,版本号就会变成3,而t1线程在去修改的时候虽然值相同,但是t1期望的版本为1,所以修改失败,
package aba; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; public class ABADemo2 { static AtomicReference<Integer> atomicInteger = new AtomicReference<>(100); static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1); public static void main(String[] args) throws InterruptedException { new Thread(() -> { atomicInteger.compareAndSet(100,101); atomicInteger.compareAndSet(101,100); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); },"t1").start(); new Thread(() -> { try { int stamp = atomicStampedReference.getStamp(); TimeUnit.SECONDS.sleep(3); System.out.println("======atomicInteger cas操作"); System.out.println(atomicInteger.compareAndSet(100, 2019)+"\t"+atomicInteger.get()); System.out.println("======atomicStampedReference cas操作"); System.out.println(atomicStampedReference.compareAndSet(100,102,stamp,stamp+1)); } catch (InterruptedException e) { e.printStackTrace(); } },"t2").start(); TimeUnit.SECONDS.sleep(2); } }
代码中定义了一个atomicInteger,和一个atomicStampedReference,可以看到的是使用了atomicStampedReference防止了ABA操作。
JUC为我们准备了AtomicInteger,AtomicLong…但是这些个类不能同时保证多个变量的原子性,如果我们需要其他类型呢?这个时候就需要AtomicRefrence(原子引用)来解决,在上面的AtomicStampedReference其实就是一个原子引用,可以传入自己的对象,
package aba; import java.util.concurrent.atomic.AtomicReference; class User{ String userName; int age; public User(String userName, int age) { this.userName = userName; this.age = age; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", age=" + age + '}'; } } public class ABADemo { public static void main(String[] args) { User z3 = new User("z3",13); User li4 = new User("li4",32); AtomicReference<User> atomicReference = new AtomicReference<>(); atomicReference.set(z3); System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.toString()); System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.toString()); } }
代码中将一个User类传入,然后直接传入期望类,以及改变的类就可以了。
在atomic包里面还有一个AtomicMarkableReference,其原理基本和AtomicStampedReference是一样的,AtomicMarkableReference不是用版本号控制的,而是用一个标记true,false来标记。
AtomicIntegerArray,看这个类的set方法,实现原理基本相同,以及AtomicLongArray
public final void set(int i, int newValue) {
unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}
最后说四个,DoubleAccumulator,DoubleAdder,LongAccumulator,LongAdder这四个类是干嘛用的?Accumulator计算,这四个是用在高并发下面的,就是很多写操作的时候,他们使用了cell,每个线程都先写在各自的cell中,然后最后做统计,也就是说,在高并发下面这四个类的速度要快于atomic的类,并且基本是用来做统计用的,并不会来单独跟新值,在低并发下面这四个类和atomic类都差不多,这中思想有点象MapReduce了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。