目录
什么是原子类
当程序更新一个变量时,如果多线程同时更新这个变量,可能得到的结果不是期望值,比如变量i=1,A线程更新i+1,B线程也更新i+1,经过两个线程操作之后可能i不等于3,而是等于2。因为A和B线程在更新变量i的时候拿到的i的值都是1,这就是线程不安全的更新操作。通常我们会使用synchronized来解决这个问题。
原子是不可分割的最小单位,故原子类可以认为其操作都是不可分割。
对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后(提供了java.util.concurrent.atomic包), 新增的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式, 这些类同样位于JUC包下的atomic包下,发展到JDk1.8,该包下共有17个类, 包括原子更新基本类型、原子更新数组、原子更新属性、原子更新引用。
1.8新增的原子类:
DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64
原子更新基本类型
发展至JDk1.8,基本类型原子类有以下几个:
AtomicBoolean、AtomicInteger、AtomicLong、DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder
大致可以归为3类:
AtomicBoolean、AtomicInteger、AtomicLong 元老级的原子更新,方法几乎一模一样
DoubleAdder、LongAdder 对Double、Long的原子更新性能进行优化提升
DoubleAccumulator、LongAccumulator 支持自定义运算
代码表示:
package atomic;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAccumulator;
public class Demo {
private static AtomicInteger num = new AtomicInteger(1);
public static void main(String[] args) {
System.out.println(num.getAndIncrement());
System.out.println(num.get());
//输入一个数字,如果比上一个输入的大,则直接返回,如果小,则返回上一个
LongAccumulator longAccumulator = new LongAccumulator((left, right) ->
left > right ? left : right,0L
);
longAccumulator.accumulate(3L);
System.out.println(longAccumulator.get()); //3
longAccumulator.accumulate(2L);
System.out.println(longAccumulator.get()); //3
longAccumulator.accumulate(5L);
System.out.println(longAccumulator.get()); //5
}
}
原子更新数组类型
通过原子的方式更新数组里的某个元素,Atomic包提供了以下3个类。
- AtomicIntegerArray:原子更新整型数组里的元素。
- AtomicLongArray:原子更新长整型数组里的元素。
- AtomicReferenceArray:原子更新引用类型数组的元素。
由于以上几个类提供的方法几乎一样,所以只用AtomicIntegerArray做代码测试。
public static void main(String[] args) {
int[] arr = new int[]{3,2};
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
atomicIntegerArray.addAndGet(1,2);
System.out.println(atomicIntegerArray.get(1)); //4
int num = atomicIntegerArray.accumulateAndGet(0,2,(left, right) ->
left > right ? left : right
);
System.out.println(num); //3
}
需要注意的是,数组arr通过构造方法传递进去,然后AtomicIntegerArray会将当前的数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。
原子更新属性
原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下3个类进行原子字段更新
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于院子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
package atomic;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
public class Demo2 {
public static void main(String[] args) {
AtomicLongFieldUpdater<User> longFieldUpdater = AtomicLongFieldUpdater.newUpdater(User.class, "id");
User user = new User(1L, "zxt");
longFieldUpdater.compareAndSet(user,1L,100L);
System.out.println("id="+user.getId()); //100
}
}
class User {
volatile long id;
String name;
public User(Long id,String name){
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
使用上述类的时候,必须遵循以下原则 字段必须是volatile类型的,在线程之间共享变量时保证立即可见 字段的描述类型是与调用者与操作对象字段的关系一致 也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。 对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。 只能是实例变量,不能是类变量,也就是说不能加static关键字。 只能是可修改变量,不能使final变量,因为final的语义就是不可修改。 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。 如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
原子更新引用类型
原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下3个类:
- AtomicReference:用于对引用的原子更新
- AtomicMarkableReference:带版本戳的原子引用类型,版本戳为boolean类型。
- AtomicStampedReference:带版本戳的原子引用类型,版本戳为int类型。
public static void main(String[] args) {
AtomicReference<User> userAtomicReference = new AtomicReference();
User user = new User(1L, "zxt");
User user2 = new User(2L, "lan");
userAtomicReference.set(user);
userAtomicReference.compareAndSet(user,user2);
User user3 = userAtomicReference.get();
System.out.println(user3.getName()); //lan
}
代码中首先构建了一个user对象,然后把user对象设置进AtomicReference中,最后调用compareAndSet()方法进行原子更新操作,实现原理同AtomicInteger里的compareAndSet方法。
ok,今天的内容就到这,感觉有收获的同学记得支持一波~~~~谢谢啦