引言
这篇文章会从基本概念中入手,首先,从volatile关键字引出原子性的概念和Atomic包,然后,介绍Atomic在使用中的用到的CAS技术和遇到的ABA问题,最后,介绍Atomic的成员和例子
基本概念
一、volatile
用volatile修饰的变量,线程在每次修改变量的时候,都会读取变量修改后的值,可以简单的理解为volatile修饰的变量保存的是变量的地址。volatile变量具有synchronized的可见性,但是不具备原子性
- 可见性:在多线程并发的条件下,对于变量的修改,其他线程中能获取到修改后的值
- 原子性:在多线程并发的条件下,对于变量的操作是线程安全的,不会受到其他线程的干扰
volatile不是线程安全的,要使volatile变量提供理想的线程安全,必须同时满足下面两个条件
- 对变量的写操作不依赖于当前值
- 该变量没有包含在具有其他变量的不变式中
比如增量操作(x++)看上去类似一个单独操作,实际上它是一个由[读取-修改-写入]操作序列组成的组合操作,必须以原子方式执行,而volatile不能提供必须的原子特性。实现正确的操作,应该使x的值在操作期间保持线程安全,而volatile变量无法实现这点
然而,Java提供了java.util.concurrent.atomic.*
包下的变量或引用,让变量或对象的操作具有原子性,在高并发的情况下,依然能保持获取到最新修改的值,常见的有AtomicBoolean
、AtomicReference
等
- volatile原理:对于值的操作,会立即更新到主存中,当其他线程获取最新值时会从主存中获取
- atomic原理:对于值的操作,是基于底层硬件处理器提供的原子指令,保证并发时线程的安全
二、Atomic
Atomic的包名为java.util.concurrent.atomic
。这个包里面提供了一组原子变量的操作类,这些类可以保证在多线程环境下,当某个线程在执行atomic的方法时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个线程执行
三、CAS
1、CAS简介
CAS指的是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。在Java并发应用中通常指CompareAndSwap
或CompareAndSet
,即比较并交换,是实现并发算法时常用到的一种技术。java.util.concurrent
包中借助CAS实现了区别于synchronized同步锁的一种乐观锁。乐观锁就是每次去取数据的时候都乐观的认为数据不会被修改,因此这个过程不会上锁,但是在更新的时候会判断一下在此期间的数据有没有更新
2、CAS思想
CAS有三个参数,当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false
3、CAS优缺点
- 系统在硬件层面保证了CAS操作的原子性,不会锁住当前线程,它的效率是很高的。但是在并发越高的条件下,失败的次数会越多,CAS如果长时间不成功,会极大的增加CPU的开销,因此CAS不适合竞争十分频繁的场景
- CAS只能保证一个共享变量的原子操作,对多个共享变量操作时,无法保证操作的原子性,这时就可以用锁,或者把多个共享变量合并成一个共享变量来操作。JDK提供了
AtomicReference
类来保证引用对象的原子性,可以把多个变量放在一个对象里来进行CAS操作
四、ABA
CAS在操作值的时候检查值是否已经变化,没有变化的情况下才会进行更新。但是如果一个值原来是A,变成B,又变成A,那么CAS进行检查时会认为这个值没有变化,但是实际上却变化了。ABA问题的解决方法是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A就变成1A-2B-3A。JDK提供了AtomicStampedReference
来解决ABA问题
Atomic成员
Atomic成员分为四大块
- 原子方式更新基本类型
- 原子方式更新数组
- 原子方式更新引用
- 原子方式更新字段
一、原子方式更新基本类型
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新长整型
简单的看下AtomicInteger提供的方法
方法名 | 方法作用 |
---|---|
get() | 直接返回值 |
set(int) | 设置数据(注意这里是没有原子性操作的) |
getAndIncrement() | 以原子方式将当前值加1,相当于线程安全的i++操作 |
incrementAndGet() | 以原子方式将当前值加1,相当于线程安全的++i操作 |
getAndDecrement() | 以原子方式将当前值减1,相当于线程安全的i–操作 |
decrementAndGet() | 以原子方式将当前值减1,相当于线程安全的–i操作 |
getAndSet(int) | 设置指定的数据,返回设置前的数据 |
addAndGet(int) | 增加指定的数据,返回增加后的数据 |
getAndAdd(int) | 增加指定的数据,返回变化前的数据 |
lazySet(int) | 仅仅当get时才会set |
compareAndSet(int, int) | 比较源数据和期望数据(参数一),若一致,则设置新数据(参数二)到源数据中并返回true,否则返回false |
以AtomicInteger为例
public class Main {
static AtomicInteger ai = new AtomicInteger(1);
public static void main(String[] args) {
//先获取,再自增
System.out.println(ai.getAndIncrement());
//先自增,再获取
System.out.println(ai.incrementAndGet());
//增加一个指定值,先add,再get
System.out.println(ai.addAndGet(5));
//增加一个指定值,先get,再set
System.out.println(ai.getAndSet(5));
}
}
输出
1
3
8
8
二、原子方式更新数组
- AtomicIntegerArray:原子更新整型数组里的元素
- AtomicLongArray:原子更新长整型数组里的元素
- AtomicReferenceArray:原子更新引用类型数组里的元素
以AtomicIntegerArray为例
public class Main {
static int[] valueArr = new int[]{
1, 2};
//AtomicIntegerArray内部会拷贝一份数组
static AtomicIntegerArray ai = new AtomicIntegerArray(valueArr);
public static void main(String[] args) {
ai.getAndSet(0, 3);
//不会修改原始数组value
System.out.println(ai.get(0));
System.out.println(valueArr[0]);
}
}
输出
3
1
三、原子方式更新引用
- AtomicReference:原子更新引用类型
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicMarkableReference:原子更新带有标记位的引用类型
以AtomicReference为例
public class Main {
public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();
public static void main(String[] args) {
User user = new User("Jack", 22);
User updateUser = new User("Rose", 20);
atomicUserRef.set(user);
atomicUserRef.compareAndSet(user, updateUser);
System.out.println(atomicUserRef.get().getName());
System.out.println(atomicUserRef.get().getOld());
}
static class User {
private String name;
private int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}
输出
Rose
20
四、原子方式更新字段
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器
- AtomicLongFieldUpdater:原子更新长整型字段的更新器
- AtomicStampedReference:原子更新带有版本号的引用类型
以AtomicIntegerFieldUpdater为例
public class Main {
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
.newUpdater(User.class, "old");
public static void main(String[] args) {
User user = new User("Hensen", 20);
System.out.println(a.getAndIncrement(user));
System.out.println(a.get(user));
}
public static class User {
private String name;
public volatile int old;//注意需要用volatile修饰
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}
输出
22
23
今天的文章java原子类有哪些_原子的基本特征[通俗易懂]分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/57903.html