美文网首页Java 并发程序员基础知识
【Java 并发笔记】13 种原子操作类相关整理

【Java 并发笔记】13 种原子操作类相关整理

作者: 58bc06151329 | 来源:发表于2019-01-15 16:12 被阅读5次

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

1. 简介

  • num++ 实际上由 读取-加一-写入 三步组成,这是个复合类的操作(volatile 无法解决 num++ 的原子性问题),在并发环境下,如果不做任何同步处理,就会有线程安全问题。最直接的处理方式就是加锁。
synchronized(this){
    num++;
 }
  • 使用独占锁机制来解决,是一种悲观的并发策略,同一刻只能有一个线程持有锁,那其他线程就会阻塞。

    • 线程的挂起恢复会带来很大的性能开销,尽管 JVM 对于非竞争性的锁的获取和释放做了很多优化,但是一旦有多个线程竞争锁,频繁的阻塞唤醒,还是会有很大的性能开销。所以使用 synchronized 不够合理。
  • 原子类的出现可以解决类似 num++ 这样的复合类操作的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小。

  • Java 从 JDK 1.5 开始提供了 java.util.concurrent.atomic 包,这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。

  • 在 atomic 包里一共提供了 13 个原子操作类,提供了 4 种类型的原子更新方式。

    • 原子更新基本类型。
    • 原子更新数组。
    • 原子更新引用。
    • 原子更新属性(字段)。

2. 原子更新

  • atomic 包里的类基本都是使用 Unsafe 实现的包装类。

2.1 原子更新基本类型

  • 使用原子的方式更新基本类型,atomic 包提供了 3 个类。
类名 说明
AtomicBoolean 原子更新布尔类型。
AtomicInteger 原子更新整型。
AtomicLong 原子更新长整型。
  • 几个类都提供了类似的方法,以 AtomicInteger 为例。
部分方法 说明
int addAndGet(int delta) 以原子方式将输入的数值与实例中的值想加,并返回结果。
boolean compareAndSet(int expect, int update) 若输入的数值等于预期值,则以原子方式将该值设置为输入的值。
int getAndIncrement() 以原子方式将当前值加1,返回的是自增前的值。
void lazySet(int newValue) 最终会设置成 new Value,使用 lazySet 设置后,可能会导致其他线程在之后的一小段时间内还是可以读到旧的值。
int getAndSet(int newValue) 以原子方式设置为 newValue 的值,并返回旧值。

addAndGet 方法

  • 最终使用了 compareAndSwapInt 方法进行更新。
public final int addAndGet(int delta) {
       return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
public final int getAndSetInt(Object o, long offset, int newValue) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, newValue));
        return v;
}

compareAndSet 方法

  • 最终使用了 compareAndSwapInt 方法进行更新。
public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

getAndIncrement 方法

  • 最终使用了 compareAndSwapInt 方法进行更新。
public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
}

lazySet 方法

  • 最终使用了 putOrderedInt 方法进行更新。
public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
}

getAndSet 方法

  • 最终使用了 compareAndSwapInt 方法进行更新。
public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
}

原子更新基础类型示例

  • 原子操作一般用来操作共享变量,所以一般是用来包装一个类的静态成员变量。
public class Test {
    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                public void run() {
                    System.out.println(atomicInteger.getAndIncrement());
                }
            }).start();
        }
        System.out.println(atomicInteger.get());
    }
}


/**
--- print ---
0
1
2
3
4
**/

2.2 原子更新数组

  • 使用原子的方式更新数组,atomic 包提供了 4 个类。
类名 说明
AtomicIntegerArray 原子更新整型数组里的元素。
AtomicLongArray 原子更新长整型数组里的元素。
AtomicReferenceArray 原子更新引用类型数组的元素。
AtomicBooleanArray 原子更新布尔类型数组的元素。
  • 几个类都提供了类似的方法。
部分方法 说明
int addAndGet(int i, int delta) 以原子方式将输入值与数组中索引 i 的元素相加。
boolean compareAndSet(int i, int expect, int update) 若当前值等于预期值,则以原子方式将数组位置 i 的元素设置成 update 值。
get(int i) 获取索引为 i 的元素值。

addAndGet 方法

  • 最终使用 compareAndSwapInt 方法进行更新。
public final int addAndGet(int i, int delta) {
        return getAndAdd(i, delta) + delta;
}
public final int getAndAdd(int i, int delta) {
        return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}

compareAndSet 方法

  • 最终使用 compareAndSwapInt 方法进行更新。
public final boolean compareAndSet(int i, int expect, int update) {
        return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
private boolean compareAndSetRaw(long offset, int expect, int update) {
        return unsafe.compareAndSwapInt(array, offset, expect, update);
}

get 方法

  • 最终使用 getIntVolatile 方法进行获取。
public final int get(int i) {
        return getRaw(checkedByteOffset(i));
}

private int getRaw(long offset) {
        return unsafe.getIntVolatile(array, offset);
}

原子更新数组示例

public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
}
  • 数组通过构造方法传递进去,然后 AtomicIntegerArray 会将当前数组
    复制一份,所以当 AtomicIntegerArray 对内部的数组元素进行修改时,不会影响传入的数组。
public class Test {

    static int[] value = new int[] { 1, 2 };
    static AtomicIntegerArray ai = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        System.out.println(ai.get(0));
        ai.getAndSet(0, 3);
        System.out.println(ai.get(0));
        System.out.println(value[0]);
    }
}

/**
--- print ---
1
3
1
**/

2.3 原子更新引用

  • 使用原子的方式更新引用,atomic 包提供了 3 个类。
类名 说明
AtomicReference 原子更新引用类型。
AtomicReferenceFieldUpdater 原子更新引用类型里的字段。
AtomicMarkableReference 原子更新带有标记位的引用类型,构造方法是AtomicMarkableReference(V initialRef,boolean initialMark)

原子更新引用示例

public class Test {

    static class User {
        private String name = "";
        private int id;

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

        public String getName() {
            return name;
        }

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

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }
    }

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

    public static void main(String[] args) {
        User user = new User("aa", 11);
        ar.set(user);
        User newUser = new User("bb", 22);
        ar.compareAndSet(user, newUser);
        System.out.println(ar.get().getName());
        System.out.println(ar.get().getId());
    }
}

/**
--- print ---
bb
22
**/

2.4 原子更新属性(字段)

  • 使用原子的方式更新属性(字段),atomic 包提供了 3 个类。
类名 说明
AtomicIntegerFieldUpdater 原子更新整型的字段的更新器。
AtomicLongFieldUpdater 原子更新长整型字段的更新器。
AtomicStampedReference 原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题

原子更新属性(字段)示例

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

    static class User {
        volatile String name = "";
        private int id;

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

        public String getName() {
            return name;
        }

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

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }
    }

    public static AtomicReferenceFieldUpdater updater =
            AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");

    public static void main(String[] args) {
        User user = new User("aa", 22);
        updater.compareAndSet(user, user.getName(), "bb");
        System.out.println(updater.get(user));
    }
}

/**
--- print ---
bb
**/

相关文章

网友评论

    本文标题:【Java 并发笔记】13 种原子操作类相关整理

    本文链接:https://www.haomeiwen.com/subject/xpuydqtx.html