并发编程——java的原子类操作

发布于:2022-12-26 ⋅ 阅读:(413) ⋅ 点赞:(0)

目录

什么是原子类

原子更新基本类型

原子更新数组类型

原子更新属性

原子更新引用类型


什么是原子类

当程序更新一个变量时,如果多线程同时更新这个变量,可能得到的结果不是期望值,比如变量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,今天的内容就到这,感觉有收获的同学记得支持一波~~~~谢谢啦


网站公告

今日签到

点亮在社区的每一天
去签到