JAVA并发-Atomic包

2021-01-03  本文已影响0人  handsomemao666

Atomic包核心

Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作。
compare and swap,比较和替换技术,将预期值与当前变量的值比较(compare),如果相等则使用新值替换(swap)当前变量,否则不作操作。
现代CPU已广泛支持CAS指令,如果不支持,那么JVM将使用自旋锁,与互斥锁一样,两者都需先获取锁才能访问共享资源,但互斥锁会导致线程进入睡眠,而自旋锁会一直循环等待直到获取锁。
另外,有一点需要注意的是CAS操作中的ABA问题,即将预期值与当前变量的值比较的时候,即使相等也不能保证变量没有被修改过,因为变量可能由A变成B再变回A,解决该问题,可以给变量增加一个版本号,每次修改变量时版本号自增,比较的时候,同时比较变量的值和版本号即可。

Atomic包主要提供四种原子更新方式

原子方式更新基本类型

包括:AtomicBoolean:原子更新布尔类型;AtomicInteger:原子更新整型;AtomicLong:原子更新长整型。
下面以AtomicInteger举个例子:

// 请求总数
public static int clientTotal = 5000;

// 同时并发执行的线程数
public static int threadTotal = 200;

public static AtomicInteger count = new AtomicInteger(0);

public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newCachedThreadPool();
    final Semaphore semaphore = new Semaphore(threadTotal);
    final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
    for (int i = 0; i < clientTotal ; i++) {
        executorService.execute(() -> {
            try {
                semaphore.acquire();
                add();
                semaphore.release();
            } catch (Exception e) {
                log.error("exception", e);
            }
            countDownLatch.countDown();
        });
    }
    countDownLatch.await();
    executorService.shutdown();
    log.info("count:{}", count.get());
}

private static void add() {
    count.incrementAndGet();
    // count.getAndIncrement();
}

注意:Atomic包提供了三种基本类型的原子更新,剩余的Java的基本类型还有char,float和double等,其更新方式可以参考AtomicBoolean的思路来现,AtomicBoolean是把boolean转成整型再调用compareAndSwapInt进行CAS来实现的,类似的short和byte也可以转成整形,float和double可以利用Float.floatToIntBits,Double.doubleToLongBits转成整形和长整形进行相应处理。

原子方式更新数组

以下三个类是以原子方式更新数组:
AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里的元素。
AtomicReferenceArray:原子更新引用类型数组里的元素。
以AtomicIntegerArray为例,其方法与AtomicInteger很像,多了个数组下标索引:

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]);
}
原子方式更新引用

以下三个类是以原子方式更新引用,与其它不同的是,更新引用可以更新多个变量,而不是一个变量:
AtomicReference:原子更新引用类型。
AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
AtomicMarkableReference:原子更新带有标记位的引用类型。
以AtomicReference为例:

public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();

public static void main(String[] args) {
    User user = new User("conan", 15);
    atomicUserRef.set(user);
    User updateUser = new User("Shinichi", 17);
    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;
    }
}
原子方式更新字段

以下三个类是以原子方式更新字段,

AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
AtomicLongFieldUpdater:原子更新长整型字段的更新器。
AtomicStampedReference:原子更新带有版本号的引用类型,用于解决使用CAS进行原子更新时,可能出现的ABA问题。
以AtomicIntegerFieldUpdater为例:

private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
        .newUpdater(User.class, "old");

public static void main(String[] args) {
    User conan = new User("conan", 10);
    System.out.println(a.getAndIncrement(conan));
    System.out.println(a.get(conan));
}

public static class User {
    private String name;
    //注意需要用volatile修饰
    public volatile int old;

    public User(String name, int old) {
        this.name = name;
        this.old = old;
    }

    public String getName() {
        return name;
    }

    public int getOld() {
        return old;
    }
}
LongAdder

jdk8中的LongAdder在并发竞争激烈的情况下,效率更高。

上一篇下一篇

猜你喜欢

热点阅读