在上一篇文章我们说到,在多线程的情况下,i++无法保证原子性,会出现问题,所以引入了AtomicInteger原子类。
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新长整型。
我们先对AtomicInteger类相关使用做个简单的介绍:
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger();
System.out.println( atomicInteger.get());
atomicInteger.set(3);
System.out.println(atomicInteger.get());
AtomicInteger atomicInteger2 = new AtomicInteger(9);
System.out.println( atomicInteger2.get());
atomicInteger2.set(4);
System.out.println(atomicInteger2.get());
}
运行程序:
我们发现当我们创建AtomicInteger对象时,如果不传值,则默认是0,也可以传入一个指定的值。
atomicInteger.get(); //获取当前值
atomicInteger.set(3); //设置当前值
常用自增、自减方法:
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
//相当于i--,看方法名就知道,获取当前值并自减
System.out.println(atomicInteger.getAndDecrement());
System.out.println(atomicInteger.get());
System.out.println("=================");
//相当于--i,看方法名就知道,先自减再获取减1后的值
System.out.println(atomicInteger.decrementAndGet());
System.out.println(atomicInteger.get());
System.out.println("=================");
//相当于i++,返回的是旧值,看方法名就知道,先获取再自增
System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.get());
System.out.println("=================");
//相当于++i,返回的是旧值,看方法名就知道,先自增再获取
System.out.println(atomicInteger.incrementAndGet());
System.out.println(atomicInteger.get());
}
运行结果:
CAS(Compare and Swap比较并交换)
CAS是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的。CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法,调用UnSafe类中的CAS方法,JVM会帮我们实现CAS汇编指令,这是一种完成依赖于硬件的功能,通过它可以实现原子性操作。由于CAS是一种系统原语,原语属于系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中是不允许被中断的,也就是是CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
compareAndSet(int expect, int update)
当期望值和真实值一样,则更新为update值,返回true;
当期望值和真实值不一样,则更新失败,返回false,我们需要重新获取主内存中的值。
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
boolean flag1 = atomicInteger.compareAndSet(5, 10);
System.out.println("flag1的值为"+flag1+",此时atomicInteger为:"+atomicInteger.get());
boolean flag2 = atomicInteger.compareAndSet(5, 25);
System.out.println("flag2的值为"+flag2+",此时atomicInteger为:"+atomicInteger.get());
}
运行结果:
我们在上一篇文章我们使用atomicInteger.getAndIncrement()来解决了多线程下a++的问题,它是如何在不加锁的情况下保证运行正常的呢。
我们可以点进去看下底层调用的是getAndAddInt方法,每调用一次加1。
1.Unsafe
Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据,Unsafe类存在sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法,其方法由native修饰,也就是说Unsafe的方法都直接调用操作系统底层资源执行相应的任务。
2.变量valueOffset为内存偏移量,Unsafe就是根据内存偏移地址获取数据的。
3.变量value用volatile修饰,保证了多线程之间的内存可见性。
我们再点到Unsafe类的getAndAddInt方法中,源码如下:
var1为当前对象
var2为内存偏移量
var4为1
var5为获取当前对象var1和内存偏移量var2在内存中的值
我们看到有一个do while去比较更新,如果内存中值和当前值相同则新增1,不相同在接着比较。从而我们也可以知道CAS的缺点:如果CAS失败就会一直进行尝试,长时间不成功,可能会跟CPU带来很大的开销。
CAS会导致”ABA问题“
ABA问题:
线程1从内存位置V中取出A,这个时候线程2也内存中取出A,进行一些操作将值变成了B,然后线程2又将位置V的数据变成A,这个时候线程1进行CAS操作的社会发现内存中还是A,则操作成功,虽然线程1操作成功,但是不代表这个过程没有问题。
代码演示:
public class ABATest {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(5);
public static void main(String[] args) {
new Thread(()->{
atomicReference.compareAndSet(5,6);
atomicReference.compareAndSet(6,5);
},"线程1").start();
new Thread(()->{
//暂停1秒,保证线程1完成一次ABA
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(5, 7)+"\t"+atomicReference.get());
},"线程2").start();
}
}
运行结果:
解决ABA问题:
引入类似乐观锁的版本号控制,除了比较预期值和内存位置的值,还要比较版本号是否正确。
atomic包就提供了AtomicStampedReference类来解决ABA问题,相较CAS引入了一个标志,在比较完预期值与内存地址值之后,再对预期标志和现有标志做比较,都通过才执行更新操作。
public class ABATest {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(5,1);
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"第一次版本号:"+atomicStampedReference.getStamp());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(5,6,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"第二次版本号:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(6,5,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"第三次版本号:"+atomicStampedReference.getStamp());
},"线程1").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"第一次版本号:"+stamp);
//暂停3秒,保证线程1完成一次ABA
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = atomicStampedReference.compareAndSet(5, 7, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"修改结果"+b+",当前版本号:"+atomicStampedReference.getStamp());
System.out.println("最终结果值为"+atomicStampedReference.getReference());
},"线程2").start();
}
}
运行结果: