基本介绍:
Atomic指一个操作不可中断,即使在多线程情况下,一个操作一旦开始,就不会被其他线程干扰。如果多线程中仅需要Atomic原子类解决的事情,就不需要synchronized重量级锁了。
原子类共四类:
-
基本类型:使用原子的方式更新基本类型
a. AtomicInteger整形原子类
b. AtomicLong长整型原子类
c. AtomicBoolean布尔原子类 -
数组类型:使用原子的方式更新数组中某个元素
a. AtomicIntegerArray:整形数组原子类
b. AtomicLongArray:长整形数组原子类
c. AtomicReferenceArray:引用类型数组原子类(即对应数组中存放的元素为对象形式) -
引用类型:使用原子的方式更新某个对象
a. AtomicReference:引用类型原子类
b. AtomicStampedReference:AtomicReference的扩展版,增加了一个参数stamp标记,这里是为了解决了AtomicInteger和AtomicLong的操作会出现ABA问题。
c. AtomicMarkableReference :与AtomicStampedReference差不多,只不过第二个参数不是用的int作为标志,而用boolean类型做标记,具体用法看后面讲解。 -
对象的属性修改类型:使用原子的方式更新某个类中某个字段
a. AtomicIntegerFieldUpdater:原子更新整形字段的更新器
b. AtomicLongFieldUpdater:原子更新长整形字段的更新器
c. AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器
使用方法:
一、基本类型原子类
由于三种类的方法基本一样,下面就以AtomicInteger 为例:
public final int set() //设一个值
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果当前值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue) //最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
public class AtomicIntegerTest {
public static void main(String[] args) {
AtomicInteger a = new AtomicInteger(0);
for (int i = 1; i < 5; i++) {
a.getAndIncrement(); // a 自增,相当于 a ++
}
//获取当前a的值
System.out.println("AtomicInteger a从0自增4次结果为:"+ a.get());
System.out.println("AtomicInteger 当前a为:"+ a.getAndDecrement() + ",并自减一次"); //a --
//获取当前a的值,并更新a为8
System.out.println("AtomicInteger a当前值为:"+a.getAndSet(8)+",并更新a为8");
//获取当前a的值,并将a加6
System.out.println("AtomicInteger a当前值为:"+a.getAndAdd(6)+",并将a加6");
a.compareAndSet(12,9); //如果a=12,就把a更新为9,否则不进行操作
System.out.println("AtomicInteger a当前值为:"+a.get());
a.compareAndSet(14,9); //如果a=14,就把a更新为9,否则不进行操作
System.out.println("AtomicInteger a当前值为:"+a.get());
}
}
二、数组类型原子类
由于三种类的方法基本一样,下面就以AtomicIntegerArray 为例:
public final int get(int i) //获取 index=i 位置元素的值
public final int set(int i, int newValue) //为 index=i 位置元素设新值
public final int getAndSet(int i, int newValue) //返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果index=i 位置的值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue) //最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
public class AtomicIntegerArrayTest {
public static void main(String[] args) {
int[] a = {1,1,1,1};
AtomicIntegerArray arr = new AtomicIntegerArray(a);
System.out.println("arr数组初始值为:" +arr.toString());
for (int i = 0; i < 4; i++) {
arr.getAndIncrement(i); //index = i位置上的值arr[i]自增,相当于 a[i] ++
}
System.out.println("arr数组每个元素都自增1后为:" +arr.toString());
//获取当前arr[1]的值
System.out.println("arr[1]的值为:"+ arr.get(1));
System.out.println("arr[2]当前值为:"+ arr.getAndDecrement(2) + ",并让arr[2]自减一次"); //a[2]--
//获取当前a[2]的值,并更新a为8
System.out.println("arr[2]当前值为:"+arr.getAndSet(2,8)+",并更新a[2]为8");
//获取当前a的值,并将a加6
System.out.println("arr[2]当前值为:"+arr.getAndAdd(2,6)+",并将a[2]加6");
arr.compareAndSet(2,12,9); //如果a[2]=12,就把a[2]更新为9,否则不进行操作
System.out.println("arr[2]当前值为:"+arr.get(2));
arr.compareAndSet(2,14,9); //如果a[2]=14,就把a[2]更新为9,否则不进行操作
System.out.println("arr[2]当前值为:"+arr.get(2));
}
}
三、引用原子类AtomicReference
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(final int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class AtomicReferenceTest {
public static void main(String[] args) {
AtomicReference<User> u = new AtomicReference<>();
User user1 = new User("金厂长",35);
User user2 = new User("木易阿婆",26);
User user3 = new User("快乐的小lau",22);
u.set(user1);
// 查看当前对象并设为新对象user2
System.out.println("当前对象为:"+u.getAndSet(user2)+",并设置新对象为user2");
System.out.println("当前对象为:"+u.get());
System.out.println("如果当前对象为user2,就把当前对象设为user3,否则不操作");
u.compareAndSet(user2,user3); //如果当前对象为user2则把当前对象设为user3
System.out.println("当前对象为:"+u.get());
}
}
四、AtomicStampedReference与AtomicMarkableReference类
ABA问题:简单讲就是多线程环境,2次读写中一个线程修改A->B,然后又B->A,另一个线程看到的值未改变,又继续修改成自己的期望值。当然我们如果不关心过程,只关心结果,那么这个就是无所谓的ABA问题。
- 为了解决ABA问题,伟大的java为我们提供了AtomicMarkableReference和AtomicStampedReference类,为我们解决了问题
- AtomicStampedReference是利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在ABA问题了,在这里我借鉴一下别人举得例子
举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是ABA问题。如果你是一个讲卫生讲文明的小伙子,不但关心水在不在,还要在你离开的时候水被人动过没有,因为你是程序员,所以就想起了放了张纸在旁边,写上初始值0,别人喝水前麻烦先做个累加才能喝水。这就是AtomicStampedReference的解决方案。
public class AtomicStampedReferenceTest {
public static void main(String[] args) throws InterruptedException{
final Integer init_Ref = 0, init_Stamp = 0;
AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(init_Ref,init_Stamp);
System.out.println("init_Ref为:"+asr.getReference() + " ====== init_Stamp为:"+asr.getStamp());
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Integer ref = asr.getReference();
Integer stamp = asr.getStamp();
//与前面AtomicReference的compareAndSet不同的是,增加了一个stamp标记比较,ref与stamp同时与
// 当前的ref、stamp相同时才进行 + 操作
System.out.println(ref + " ====== " + stamp + " ====== "
+ asr.compareAndSet(ref, ref + 10, stamp, stamp + 1));
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Integer ref = asr.getReference();
// 当前的ref相同,但此时版本号不同,操作不执行返回false
System.out.println(ref + " ====== " + init_Stamp + " ====== "
+ asr.compareAndSet(ref, ref + 10, init_Stamp, init_Stamp + 1));
}
});
t1.start();
t1.join(); //只是为了让代码有序执行
t2.start();
t2.join();
System.out.println("最终的结果为:"+ asr.getReference() + " ====== " + asr.getStamp());
}
}
注:以上AtomicStampedReference部分摘自参考文章1, 本来看到这部分就想放弃了,但是翻到了这篇文章,讲的挺清晰的,就又继续看下去了。
可以看出,第一次ref、stamp都与输入值相等,因此执行ref+10,和stamp+1,此时ref=10,stamp=1。第二个线程中输入的ref与当前ref值相同,但是init_Stamp=0 与当前stamp=1 不等,因此不执行。总的来说就是除了对比ref,又增加了一个stamp来判断到底操不操作。
AtomicMarkableReference与AtomicStampedReference不同的是将int stamp改为了boolean类型的mark做标记。同样的例子:
public class AtomicMarkableReferenceTest {
public static void main(String[] args) throws InterruptedException{
final Integer init_Ref = 0;
final Boolean init_Mark = false;
AtomicMarkableReference<Integer> amr = new AtomicMarkableReference<>(init_Ref,init_Mark);
System.out.println("init_Ref为:"+amr.getReference() + " ====== init_Mark为:"+amr.isMarked());
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Integer ref = amr.getReference();
Boolean mark = amr.isMarked();
//与前面AtomicReference的compareAndSet不同的是,增加了一个stamp标记比较,ref与stamp同时与
// 当前的ref、stamp相同时才进行 + 操作
System.out.println(ref + " ====== " + mark + " ====== "
+ amr.compareAndSet(ref, ref + 10, mark, true));
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Integer ref = amr.getReference();
// 当前的ref相同,但此时版本号不同,操作不执行返回false
System.out.println(ref + " ====== " + init_Mark + " ====== "
+ amr.compareAndSet(ref, ref + 10, init_Mark, true));
}
});
t1.start();
t1.join(); //只是为了让代码有序执行
t2.start();
t2.join();
System.out.println("最终的结果为:"+ amr.getReference() + " ====== " + amr.isMarked());
}
}
五、对象属性修改类型
以AtomicIntegerFieldUpdater 为例介绍一下简单使用方法:
public class User {
private String name;
volatile int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(final int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) {
User user = new User("菜鸡",28);
AtomicIntegerFieldUpdater<User> u = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
u.addAndGet(user,5);
System.out.println(user.toString());
}
}
这里值得注意的是:使用AtomicIntegerFieldUpdater.newUpdater
修改属性时:
- 被修改的属性必须是
volatile
类型的,在线程之间共享变量时保证立即可见, - 属性的修饰符(public/protected/default/private)要保证当前操作对该属性可以直接进行,比如当我们用
private volatile int age
时就会报错,因为private修饰时,外部无法访问也无法修改。 - 只能是实例变量,不能是类变量,也就是说不能加static关键字。
- 只能是可修改变量,不能使final变量,因为final的语义就是不可修改。
- 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
具体分析可以参考:参考文章3
参考文章:
- https://blog.csdn.net/zhaozhirongfree1111/article/details/72781758
- https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484834&idx=1&sn=7d3835091af8125c13fc6db765f4c5bd&source=41#wechat_redirect
- https://blog.csdn.net/zhaozhirongfree1111/article/details/72781147