标签:CAS int 更新 原子 并发 AtomicInteger Atomic 之五 public
前言
我们在并发举措之二之volatile中讲过,volatile关键字修饰的变量可以使不用线程之间可见,且volatile 禁止了指令重排。但是volatile关键字修饰的变量在运行中还可能出现线程不安全的情况,根本原因在与对变量的操作不能保证其原子性,java.util.concurrent
包提供了一组原子操作的封装类,它们位于java.util.concurrent.atomic
包。常见的有AtomicInteger、AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等,他们的实现原理相同,区别在与运算对象类型的不同。令人兴奋地,还可以通AtomicReference<V>将一个对象的所有操作转化成原子操作。
我们知道,在多线程程序中,诸如++i 或i++等运算不具有原子性,是不安全的线程操作之一。通常我们会使用synchronized将该操作变成一个原子操作,但JVM为此类操作特意提供了一些同步类,使得使用更方便,且使程序运行效率变得更高。通过相关资料显示,通常AtomicInteger的性能是ReentantLock的好几倍。
一、实现机制
我们以AtomicInteger
为例,它提供的主要操作有:
- 增加值并返回新值:
int addAndGet(int delta)
- 加1后返回新值:
int incrementAndGet()
- 获取当前值:
int get()
- 用CAS方式设置:
int compareAndSet(int expect, int update)
Atomic类是通过无锁(lock-free)的方式实现的线程安全(thread-safe)访问。它的主要原理是利用了CAS:Compare and Set。
如果我们自己通过CAS编写incrementAndGet()
,它大概长这样:
public int incrementAndGet(AtomicInteger var) {
int prev, next;
do {
prev = var.get();
next = prev + 1;
} while ( ! var.compareAndSet(prev, next));
return next;
}
CAS是指,在这个操作中,如果AtomicInteger
的当前值是prev
,那么就更新为next
,返回true
。如果AtomicInteger
的当前值不是prev
,就什么也不干,返回false
。通过CAS操作并配合do ... while
循环,即使其他线程修改了AtomicInteger
的值,最终的结果也是正确的。
二、atomic分类
基本类型类:用于通过原子的方式更新基本类型
AtomicBoolean 原子更新布尔类型
AtomicInteger 原子更新整型
AtomicLong 原子更新长整型
数组:通过原子的方式更新数组里的某个元素
AtomicIntegerArray 原子更新整型数组里的元素
AtomicLongArray 原子更新长整型数组里的元素
AtomicReferenceArray 原子更新引用类型数组里的元素
引用类型:如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类
AtomicReference 原子更新引用类型
AtomicReferenceFieldUpdater 原子更新引用类型里的字段
AtomicMarkableReference 原子更新带有标记位的引用类型
字段类: 如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类
AtomicIntegerFieldUpdater 原子更新整型的字段的更新器
AtomicLongFieldUpdater 原子更新长整型字段的更新器
AtomicStampedReference 原子更新带有版本号的引用类型
三、atomic应用
3.1、编写一个多线程安全的全局唯一ID生成器:
class IdGenerator {
AtomicLong var = new AtomicLong(0);
public long getNextId() {
return var.incrementAndGet();
}
}
3.2、AtomicIntegerArray应用
public class AtomicIntegerArrayTest {
static int[] value = new int[] { 1, 2 };
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
ai.getAndSet(0, 3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
}
AtomicIntegerArray类需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。
3.3、AtomicReference应用
public class AtomicReferenceTest {
public static void main(String[] args) {
AtomicReference<Student> atomicReference = new AtomicReference();
Student student = new Student("张三", 18);
atomicReference.set(student);
Student updateStudent = new Student("李四", 19);
atomicReference.compareAndSet(student, updateStudent);
System.out.printf("name:%s,age:%d \n",atomicReference.get().getName(),atomicReference.get().getAge());
}
static class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}
四、优缺点
- CAS相对于其他锁,不会进行内核态操作,有着一些性能的提升。但同时引入自旋,当锁竞争较大的时候,自旋次数会增多。cpu资源会消耗很高。
- CAS+自旋适合使用在低并发有同步数据的应用场景。
- 在Java 8中引入了4个新的计数器类型,LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator。他们都是继承于Striped64。,
- 在LongAdder 与AtomicLong有什么区别?Atomic*遇到的问题是,只能运用于低并发场景。因此LongAddr在这基础上引入了分段锁的概念。当竞争不激烈的时候,所有线程都是通过CAS对同一个变量(Base)进行修改,当竞争激烈的时候,会将根据当前线程哈希到对于Cell上进行修改(多段锁)。
标签:CAS,int,更新,原子,并发,AtomicInteger,Atomic,之五,public 来源: https://blog.csdn.net/heijunwei/article/details/113184097
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。