java Atomic类使用

JUC包中除了锁,还提供了原子操作类来实现线程对临界资源的互斥访问。

Atomic包中提供了多种类型的原子操作类:
在这里插入图片描述
它们都是CAS(compareAndSwap)来实现原子性。

1.原子基本类型
用于原子更新基本类型,包括以下三类:

  • AtomicBoolean:原子更新布尔类型
  • AtomicInteger:原子更新整数类型
  • AtomicLong:原子更新长整数类型

AtomicInteger 常用方法:

  • int incrementAndGet():将当前值加1并返回新值。
  • int decrementAndGet():将当前值减1并返回新值
  • int updateAndGet(IntUnaryOperator updateFunction):将当前值原子执行用户自定义的操作后并返回新值。(updateFunction是用户自定义操作)
public class AtomicIntegerTest {

    public static void main(String[] args) {
        AtomicInteger integer = new AtomicInteger(0);
        //递增
        System.out.println("递增后的值: " + integer.incrementAndGet());
        //递减
        System.out.println("递减后的值: " + integer.decrementAndGet());
        //增加20
        System.out.println("增加20后的值: " + integer.updateAndGet((x)-> x + 20));
    }
}

运行结果:

递增后的值: 1
递减后的值: 0
增加20后的值: 20

2.原子引用类型
原子基本类型只能保证基本类型的原子性,如果要原子更新一个对象就需要使用原子引用类型。

原子引用类型如下:

  • AtomicReference:原子更新引用类型
  • AtomicMarkableReference:AtomicMarkableReference跟AtomicStampedReference差不多,AtomicStampedReference关心的是动过几次,AtomicMarkableReference关心的是有没有被人动过。
  • AtomicReferenceFieldUpdater:原子更新用类型中的字段。
  • AtomicStampedReference:原子更新引用类型,只是该引用是带版本号的,用于解决ABA问题。

AtomicReference 主要方法:

  • V get():获取当前引用的值。
  • boolean compareAndSet(V expect, V update):如果当前引用等于预期引用,则将当前引用更新为新值。

下面以多个线程更新账户余额的例子来说明AtomicReference的使用

/*账户余额*/
class AccountBalance {
    private BigDecimal banlance;

    public AccountBalance(BigDecimal banlance) {
        this.banlance = banlance;
    }

    public BigDecimal getBanlance() {
        return banlance;
    }

    public void setBanlance(BigDecimal banlance) {
        this.banlance = banlance;
    }
}

class UpdateBalanceRunnable implements  Runnable {
    private AccountBalance accountBalance;
    private AtomicReference<AccountBalance> reference;

    public UpdateBalanceRunnable(AtomicReference<AccountBalance> reference,
                                 AccountBalance accountBalance) {
        this.reference = reference;
        this.accountBalance = accountBalance;
        this.reference.set(this.accountBalance);
    }


    @Override
    public void run() {
            while(true) {
                AccountBalance prev = reference.get();
                // 账户余额加10
                AccountBalance next = new AccountBalance(prev.getBanlance().
                        add(new BigDecimal(10.0)));
                if (reference.compareAndSet(prev, next)){
                    //更新成功,退出,否则自旋
                    break;
                }
            }
    }
}

public class AtomicReferenceTest {

    public static void main(String[] args) throws InterruptedException {
        AccountBalance first = new AccountBalance(new BigDecimal(0));
        System.out.println("更新前账户余额: " + first.getBanlance());
        
        AtomicReference<AccountBalance> reference = new AtomicReference<>(first);
        UpdateBalanceRunnable runnable = new UpdateBalanceRunnable(reference,first);
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println("更新后账户余额: " + reference.get().getBanlance());
    }
}

运行结果:

更新前账户余额: 0
更新后账户余额: 20

AtomicStampedReference主要方法:

  • V get(int[] stampHolder):获取指定版本的值
  • public V getReference():返回当前版本的值,也就是最新值。
  • public int getStamp():返回当前版本戳。
  • public boolean compareAndSet(V expectedReference, V newReference,
    int expectedStamp, int newStamp):如果当前引用 等于 预期值并且 当前版本戳等于预期版本戳, 将更新新的引用和新的版本戳到内存
  • public boolean attemptStamp(V expectedReference, int newStamp):如果当前引用 等于 预期引用, 将更新新的版本戳到内存
  • public void set(V newReference, int newStamp) :设置当前引用的新引用和版本戳
public class AtomicStampedReferenceTest {
    public static void main(String[] args) {
        Integer integer1 = 0;
        Integer integer2 = 2;
        AtomicStampedReference<Integer> reference = new AtomicStampedReference<Integer>(integer1,1);
        reference.compareAndSet(integer1,integer2,reference.getStamp(),reference.getStamp()+1);
        System.out.println("更新value后:" + reference.getReference());

        boolean b = reference.attemptStamp(integer2, reference.getStamp() + 1);
        System.out.println("更新版本后: "+reference.getStamp());

        boolean c = reference.compareAndSet(integer2,3,4, reference.getStamp()+1);
        if (!c) {
            System.out.println("CAS 操作失败");
        }
    }
}

运行结果:

更新value后:2
更新版本后: 3
CAS 操作失败

3.原子更新数组类型
Atomic包同样提供了对数组类型原子操作的类,主要包括:

  • AtomicIntegerArray:整型数组原子操作类
  • AtomicLongArray:长整型数组原子操作类
  • AtomicReferenceArray:对象数组原子操作类

AtomicIntegerArray主要方法:

  • int incrementAndGet(int i):将数组位置i的元素加1
  • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值

下面是一个多线程更新数组的例子。

public class AtomicIntegerArrayTest {
    
    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerArray array = new AtomicIntegerArray(10);
        List<Thread> list = new ArrayList<Thread>();

        System.out.println("数组未修改前:");
        for (int i = 0; i < array.length(); ++i) {
            System.out.print(array.get(i) + "\t");
        }

        for (int i = 0; i < 10; ++i) {
            Thread t = new Thread(() -> {
            //依次更新数组
               for (int j = 0 ; j < array.length(); ++j) {
                   array.incrementAndGet(j);
               }
            });

            list.add(t);
            t.start();
        }

        for (Thread t: list) {
            t.join();
        }

        System.out.println("\n数组被修改后:");
        for (int i = 0; i < array.length(); ++i) {
            System.out.print(array.get(i) + "\t");
        }
    }
}

运行结果:

数组未修改前:
0	0	0	0	0	0	0	0	0	0	
数组被修改后:
100	100	100	100	100	100	100	100	100	100	

猜你喜欢

转载自blog.csdn.net/cl2010abc/article/details/105456217