所有分类
  • 所有分类
  • 未分类

Java–原子类(atomic)的用法(有实例)

简介

说明

本文用示例介绍Java的原子类的用法。

原子类属于JUC。

原子类由CAS操作保证原子性,由volatile关键字保证可见性。

原子类自jdk 1.5开始出现,位于java.util.concurrent.atomic包下面。jdk 1.8又新增了4个性能更好的原子类。

原子类类型

  • 基本类型
    • AtomicBoolean
    • AtomicInteger
    • AtomicLong
  • 引用类型
    • AtomicReference
    • AtomicStampedReference
    • AtomicReferenceFieldUpdater
    • AtomicMarkableReference
  • 数组类型
    • AtomicIntegerArray
    • AtomicLongArray
    • AtomicReferenceArray
  • 字段类型
    • AtomicIntegerFieldUpdater
    • AtomicLongFieldUpdater
    • AtomicStampedFieldUpdater
  • JDK8新增原子类简介
    • DoubleAccumulator
    • LongAccumulator
    • DoubleAdder
    • LongAdder

原子更新基本类型

类型

  • AtomicBoolean:  原子更新布尔类型。 AtomicBoolean 是把Boolean转成整型,再使用 compareAndSwapInt 进行操作的。
  • AtomicInteger:    原子更新整型。 
  • AtomicLong:       原子更新长整型。 

方法

以上3个类提供的方法几乎一模一样,以AtomicInteger为例进行详解,AtomicIngeter的常用方法如下: 

  • int addAndGet(int delta): 以原子的方式将输入的数值与实例中的值相加,并返回结果。 
  • boolean compareAndSet(int expect, int update): 如果输入的值等于预期值,则以原子方式将该值设置为输入的值。 
  • int getAndIncrement(): 以原子的方式将当前值加 1,注意,这里返回的是自增前的值,也就是旧值。 
  • void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 
  • int getAndSet(int newValue): 以原子的方式设置为newValue,并返回旧值。

代码示例1

package org.example.a;

import java.util.concurrent.atomic.AtomicInteger;

public class Demo {
    static AtomicInteger ai = new AtomicInteger(1);
    public static void main(String[] args) {
        System.out.println(ai.getAndIncrement());
        System.out.println(ai.get());
    }
}

输出结果

1
2

代码示例2

package org.example.a;

import java.util.concurrent.atomic.AtomicLong;

class Counter implements Runnable{
    private static AtomicLong atomicLong = new AtomicLong(0);
    @Override
    public void run() {
        System.out.println(atomicLong.incrementAndGet());
    }
}

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Counter[] threads = new Counter[50];

        for(int i = 0; i < 50; i++){
            threads[i] = new Counter();
        }
        for(int i = 0; i < 50; i++){
            new Thread(threads[i]).start();
        }
    }
}

 输出结果

1
2
4
3
5
...
50
48
49

原子更新引用类型

简介

原子更新基本类型的AtomicInteger,只能更新一个值,如果更新多个值,比如更新一个对象里的值,那么就要用原子更新引用类型提供的类。

类型

Atomic包提供了以下类:

  • AtomicReference:  原子更新引用类型。 
  • AtomicStampedReference: 原子更新引用类型(带时间戳,避免ABA问题)
  • AtomicReferenceFieldUpdater:  原子更新引用类型的字段。 
  • AtomicMarkableReference:  原子更新带有标记位的引用类型,可使用构造方法更新一个布尔类型的标记位和引用类型。

代码示例

package com.knife.example.demo;
 
import lombok.Data;

import java.util.concurrent.atomic.AtomicReference;
 
 
public class Demo {
    private static AtomicReference<User> atomicReference = new AtomicReference<User>();
 
    public static void main(String[] args) {
        User u1 = new User("Hello", 18);
        atomicReference.set(u1);
        System.out.println(atomicReference.get().getName() + " " 
            + atomicReference.get().getAge());

        User u2 = new User("World", 15);
        atomicReference.compareAndSet(u1, u2);
        System.out.println(atomicReference.get().getName() + " " 
            + atomicReference.get().getAge());
        System.out.println(u1.getName() + " " + u1.getAge());
    }
}

@Data
class User {
    private String name;
    private int age;
 
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

输出结果

Hello 18
World 15
Hello 18

代码分析

我们把对象放到 AtomicReference 中,然后调用 compareAndSet () 原子操作替换,原理和 AtomicInteger 相同,只是调用的是 compareAndSwapObject()  方法。

原子更新数组

类型

AtomicIntegerArray:  原子更新整型数组里的元素。 
AtomicLongArray:  原子更新长整型数组里的元素。 
AtomicReferenceArray:  原子更新引用类型数组里的元素。

方法

  • get(int index):获取索引为index的元素值。 
  • compareAndSet(int i, int expect, int update): 如果当前值等于预期值,则以原子方式将数组位置 i 的元素设置为update值。 

实例

package org.example.a;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class Demo {
    static int[] value =new int[]{1,2};
    static AtomicIntegerArray ai =new AtomicIntegerArray(value);
    public static void main(String[] args) {
        ai.getAndSet(0,2);
        System.out.println(ai.get(0));
        System.out.println(value[0]);
    }
}

输出结果

2
1

原子更新字段

简介

如果需要原子的更新类里某个字段时,需要用到原子更新字段类。

类型

  • AtomicIntegerFieldUpdater:  原子更新整型的字段的更新器。 
  • AtomicLongFieldUpdater:  原子更新长整型字段的更新器。 
  • AtomicStampedFieldUpdater:  原子更新带有版本号的引用类型。

实例

package com.example.a;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class Demo {
    //创建原子更新器,并设置需要更新的对象类和对象的属性
    private static AtomicIntegerFieldUpdater<User> ai = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

    public static void main(String[] args) {
        User u1 = new User("Tony", 18);
        //原子更新年龄,+1
        System.out.println(ai.getAndIncrement(u1));
        System.out.println(u1.getAge());
    }
}

class User {
    private String name;
    public volatile int age;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

输出结果

18
19

代码详解

要想原子地更新字段类需要两步。第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新类的字段必须使用 public volatile 修饰

JDK8新增原子类简介

新增的类

  • DoubleAccumulator
  • LongAccumulator
  • DoubleAdder
  • LongAdder

实例

package org.example.a;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

public class Demo {
    private static ExecutorService service;

    //从初始值0开始,做加/减处理,每次加/减1
    private static LongAdder adder = new LongAdder();

    //从初始值0开始,做累加处理
    private static LongAccumulator accumulator1 = new LongAccumulator((x, y) -> x+y, 0);

    //从初始值1开始,做累积处理
    private static LongAccumulator accumulator2 = new LongAccumulator((x, y) -> x*y, 1);

    public static void main(String[] args) {
        service = Executors.newCachedThreadPool();

        for(int i=0; i<10; i++){
            service.submit(() -> {
                adder.increment(); //加1
                System.out.println(adder.intValue());

                accumulator1.accumulate(2); //每次加2
                System.out.println(accumulator1.get()); //获取当前值

                accumulator2.accumulate(2); //每次 * 2
                System.out.println(accumulator2.get()); //获取当前值

            });
        }

        service.shutdown();
    }
}

执行结果

1
2
2
4
3
...
5
16
256
9
18
512
10
20
1024

注意事项

下面以 LongAdder 为例介绍一下,并列出使用注意事项

这些类对应把 AtomicLong 等类的改进。比如 LongAccumulator 与 LongAdder 在高并发环境下比 AtomicLong 更高效。Atomic、Adder在低并发环境下,两者性能很相似。但在高并发环境下,Adder 有着明显更高的吞吐量,但是有着更高的空间复杂度。

sum()  方法要在没有并发的情况下调用,如果在并发情况下使用会存在计数不准。

LongAdder不可以代替AtomicLong  ,虽然 LongAdder 的 add() 方法可以原子性操作,但是并没有使用 Unsafe 的CAS算法,只是使用了CAS的思想。

LongAdder其实是LongAccumulator的一个特例,LongAccumulator提供了比LongAdder更强大的功能,构造函数其中accumulatorFunction一个双目运算器接口,根据输入的两个参数返回一个计算值,identity则是LongAccumulator累加器的初始值。

LongAdder是内部维护多个变量,每个变量初始化都0,在同等并发量的情况下,争夺单个变量的线程量会减少这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后获取当前值时候是把所有变量的值累加后返回的。

0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录