赞
踩
基本类型
使用原子的方式更新基本类型
AtomicInteger
:整形原子类AtomicLong
:长整型原子类AtomicBoolean
:布尔型原子类数组类型
使用原子的方式更新数组里的某个元素
AtomicIntegerArray
:整形数组原子类AtomicLongArray
:长整形数组原子类AtomicReferenceArray
:引用类型数组原子类引用类型
AtomicReference
:引用类型原子类AtomicStampedReference
:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。AtomicMarkableReference
:原子更新带有标记位的引用类型对象的属性修改类型
AtomicIntegerFieldUpdater
:原子更新整形字段的更新器AtomicLongFieldUpdater
:原子更新长整形字段的更新器AtomicReferenceFieldUpdater
:原子更新引用类型字段的更新器JUC中多数类是通过volatile和CAS来实现的,CAS本质上提供的是一种无锁方案,而Synchronized和Lock是互斥锁方案; java原子类本质上使用的是CAS,而CAS底层是通过Unsafe类实现的。所以需要对CAS, Unsafe和原子类详解
CAS的全称为Compare-And-Swap,直译就是对比交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。
简单解释就是:
真实值和期望值相同,就修改成功,真实值和期望值不同,就修改失败!
以AtomicInteger
为例去理解CAS的概念:
AtomicInteger atomicInteger = new AtomicInteger(2020); // 旧值
boolean ato = atomicInteger.compareAndSet(2020, 2021); // 分别为期望值和新值,ato为true
System.out.println(atomicInteger.get()); // 结果为:2021
public final boolean compareAndSet(int expect, int update)
// 如果我期望值的达到了,那么就更新,否则,就不更新(会一直循环,一直往下看源码可知),CAS是CPU的并发原语
CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁,JDK中大量使用了CAS来更新数据而防止加锁(synchronized 重量级锁)来保持原子更新。
如果不使用CAS,在高并发下,多线程同时修改一个变量的值我们需要synchronized加锁(可能有人说可以用Lock加锁,Lock底层的AQS也是基于CAS进行获取锁的)。
public class Test {
private int i=0;
public synchronized int add(){
return i++;
}
}
java中为我们提供了AtomicInteger 原子类(底层基于CAS进行更新数据的),不需要加锁就在多线程并发场景下实现数据的一致性。效率提高了很多
public class Test {
private AtomicInteger i = new AtomicInteger(0);
public int add(){
return i.addAndGet(1);
}
}
乐观锁
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁。但是在更新的时候会判断一下再此期间别人有没有去更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能执行更新。
悲观锁
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。
CAS 方式为乐观锁,synchronized 为悲观锁。因此使用 CAS 解决并发问题通常情况下性能更优。
但使用 CAS 方式也会有几个问题:
1、 问题说明
比如说一个线程one从内存位置V中取出A,这个时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将 V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
2、解决方案
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A->B->A就会变成1A->2B->3A。
从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前版本是否等于预期版本,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)
public static void main(String[] args) { // AtomicStampedReference 注意:如果泛型是一个包装类,注意对象的引用问题 AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<Integer>(1,2); // 当前引用为1,当前版本为2 new Thread(()->{ int stamp = atomicReference.getStamp();//获得版本号为2 System.out.println("A1->"+stamp); // (1) A1->2 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 当前引用等于预期引用,当前版本等于预期版本,满足条件,则将当前引用改为2,当前版本加1等于3 System.out.println(atomicReference.compareAndSet(1, 2, atomicReference.getStamp(), atomicReference.getStamp() + 1)); // (3) true System.out.println("A2->"+atomicReference.getStamp()); // (3) A2->3 // 当前引用等于预期引用,当前版本等于预期版本,满足条件,则将当前引用改为1,当前版本加1等于4 System.out.println(atomicReference.compareAndSet(2, 1, atomicReference.getStamp(), atomicReference.getStamp() + 1)); // (4) true System.out.println("A3->"+atomicReference.getStamp()); (4) A3->4 },"A").start(); new Thread(()->{ int stamp = atomicReference.getStamp();//获得版本号为2 System.out.println("B1->"+stamp); //(2) B1->2 new ReentrantLock(true); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } // 当前引用(1)等于预期引用(1),但是当前版本(4)不等于预期版本(2)(我们的预期版本应该是B1刚开始拿的,为2),不满足条件,所以当前引用依然是1,当前版本依然是4,成功解决ABA问题 System.out.println(atomicReference.compareAndSet(1, 6, // (5) flase stamp, stamp + 1)); System.out.println("B2->"+atomicReference.getStamp()); // (5) B2->4 },"B").start(); }
结果:
A1->2
B1->2
true
A2->3
true
A3->4
false
B2->4
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升。pause指令有两个作用:第一,它可以延迟流水线执行命令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零;第二,它可以避免在退出循环的时候因内存顺序冲突(Memory Order Violation)而引起CPU流水线被清空(CPU Pipeline Flush),从而提高CPU的执行效率
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。 还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i = 2,j = a,合并一下ij = 2a,然后用CAS来操作ij。 从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法(作用),如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用(优点)。
但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”(缺点),因此对Unsafe的使用一定要慎重
Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类,下面将对其相关方法和应用场景进行详细介绍
我们首先看一下AtomicInteger下的CAS命令,AtomicInteger.compareAndSet(期望值,更新值)
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
可以看到它是调用了UnSafe类下的compareAndSwapInt(this, valueOffset, expect, update)
方法,继续往下看
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
compareAndSwapInt()
是一个本地方法
在UnSafe类中存在着这些方法,它们是通过调用compareAndSwapInt()
实现的,同时也是CAS的原理所在
以getAndAddInt()
为例
public final int getAndAddInt(Object var1, long var2, int var4) { // var4是偏移量
int var5; // var5是期望值
do {
// 获取传入对象的地址
var5 = this.getIntVolatile(var1, var2);
// 比较并交换,如果var1,var2 还是原来的 var5,就执行内存偏移+1; var5 + var4
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
从源码中发现,内部使用自旋的方式进行CAS更新(while循环进行CAS更新,如果更新失败,则循环再次重试)。
又从Unsafe类中发现,原子操作其实只支持下面三个方法
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
我们发现Unsafe只提供了3种CAS方法:compareAndSwapObject
、compareAndSwapInt
和compareAndSwapLong
。都是native
方法。
public final int get():获取当前的值
public final int getAndSet(int newValue):获取当前的值,并设置新的值
public final boolean compareAndSet(int expect, int update):当前值和期望值比较,相同则设置新值
public final int getAndIncrement():获取当前的值,并自增
public final int getAndDecrement():获取当前的值,并自减
public final int getAndAdd(int delta):获取当前的值,并加上预期的值
void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
传统的Integer需要加synchronized锁,才能保证原子性,但是synchronized锁是重量级锁,效率较低
private volatile int count = 0;
// 若要线程安全执行执行 count++,需要加锁
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
使用 AtomicInteger 后:不需要加锁,也能够保证原子性,实现线程安全
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
// 使用 AtomicInteger 后,不需要加锁,也可以实现线程安全
public int getCount() {
return count.get();
}
public class AtomicInteger extends Number implements java.io.Serializable { private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { //用于获取value字段相对当前对象的“起始地址”的偏移量 valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; //返回当前值 public final int get() { return value; } //递增加detla public final int getAndAdd(int delta) { //三个参数,1、当前的实例 2、value实例变量的偏移量 3、当前value要加上的数(value+delta)。 return unsafe.getAndAddInt(this, valueOffset, delta); } //递增加1 public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } ... }
我们可以看到 AtomicInteger 底层用的是volatile的变量和CAS来进行更改数据的。
简单解释就是:
真实值和期望值相同,就修改成功,真实值和期望值不同,就修改失败!
以AtomicInteger
为例去理解CAS的概念:
AtomicInteger atomicInteger = new AtomicInteger(2020); // 旧值
boolean ato = atomicInteger.compareAndSet(2020, 2021); // 分别为期望值和新值,ato为true
System.out.println(atomicInteger.get()); // 结果为:2021
public final boolean compareAndSet(int expect, int update)
// 如果我期望值的达到了,那么就更新,否则,就不更新(会一直循环,一直往下看源码可知),CAS是CPU的并发原语
CAS+volatile
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法(作用),如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用(优点)。
但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”(缺点),因此对Unsafe的使用一定要慎重
原子更新引用类型, 内部使用Pair来存储元素值及其版本号,可以解决ABA问题
内部使用Pair来存储元素值及其版本号
AtomicMarkableReference
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。