Java atomic operations class, you know how much?

Introduction atomic operations class

Because synchronized is uses a pessimistic locking strategy, it is not a particularly efficient solution. In fact, at JUC Atomic packet provides a simple sequence of operations, high performance, and to ensure thread safety class to update the basic types of variables, array element, and updating the reference field type object type. These classes are employed in the atomic packet is optimistic locking strategy to atomic data updating, the CAS operation is used in Java implementation.

CASE

With the development of hardware instruction set, we can use the optimistic concurrency strategy based Collision Detection: first to do if there are no other thread contention for shared data, and that the operation was successful, otherwise take compensatory measures (keep retrying until it succeeds until). Many realize this optimistic concurrency strategy does not need to be thread is blocked, so this operation is called synchronous non-blocking synchronization. Optimistic locking required operations and collision detection of these two steps have the atomicity , where you can not use a mutex to ensure the synchronization, can only rely on hardware. Hardware support for atomic operations are the most typical: compare and swap (Compare-and-Swap, CAS ). CAS instruction requires three operands, namely memory address V, the expected values of the old and the new value B. A When the operation is performed only when the value of V is equal to A, it updates the value of the V B.

// known CAS 
@ var1 object belongs is the comparison value, compare var2 required value (the actual address offset used is achieved), 
// if the object var1 var2 offset value is equal to var4 , then the value is set at the var5 and returns true, if not equal var4 false is returned. 
public final native boolean compareAndSwapInt (Object var1 , long var2, int var4, int var5);
  • CAS issues

1.ABA problem

If a variable when reading the initial value is A, its value is changed to B, and later was changed back to A, CAS operation will mistakenly believe that it has never been changed.

JUC package provides atom marked with a reference class AtomicStampedReference to solve this problem, it can be ensured by controlling the correctness of the CAS version of the variable value. ABA problem does not affect program concurrent validity in most cases, if the need to address the ABA problem, use the traditional mutual exclusion synchronization may be more efficient than the atomic class.

2. The spin-long overhead big time

Spin CAS (ie, unsuccessful execution until the cycle has been successful ) if not successful for a long time, it will bring a very large execution overhead CPU. If the JVM can support pause instructions provided by the processor so there will be some efficiency improvement, pause command serves two purposes, first it may delay pipelined execution of instructions (de-pipeline), the CPU does not consume too many resources for implementation, delay time implementation dependent, the delay time on the number of processors is zero. It can avoid a second exit loop when the order due to memory conflicts (memory order violation) caused by CPU pipeline is cleared (CPU pipeline flush), in order to improve the efficiency of the CPU.

3. a shared variable can only guarantee atomic operations  CAS valid only for a single shared variable , when the operation involves a plurality of shared variables across CAS invalid. But from JDK 1.5, is provided to ensure AtomicReference class reference atom between objects, it can be packaged into an object in the plurality of variables CAS operation is performed, so we can use the lock or by a plurality of shared variables based AtomicReference encapsulated a shared variable to operate.

  • synchronized VS CAS

Veteran of the synchronized (before non-optimized) The main question is: blocking and performance issues thread wake locks will be brought in the presence of the thread competition, because it is a mutually exclusive synchronous (blocking synchronization) . The CAS is not arbitrary thread hangs when CAS will conduct certain operations failed attempts, rather than time-consuming wake of the pending operation, and therefore also called non-blocking synchronization . This is the main difference between the two.

Atomic update base class

atomic atomic update package substantially improve the utility classes classes, as follows:

AtomicBoolean // updated atomically update the Boolean 

AtomicIntege // updated atomically update the Integer 

AtomicLong // updated atomically update the Long
  • AtomicInteger an example to summarize the commonly used methods:

addAndGet (int delta) // atomically input value and the original value by adding instance, and returns the final result 

incrementAndGet () // atomically original value will be added to the Example 1 operation, and returns the final result of the addition 

getAndSet (int newValue) // update the instance to new value and return the old value 

getAndIncrement () // atoms manner of example 1 added to the original value, return is incremented before the old value

The AtomicInteger getAndIncrement () method source code as follows:

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

Actually called a getAndAddInt method unsafe instance, obtained by static method UnSafe class getUnsafe getting unsafe instance:

private static final Unsafe unsafe = Unsafe.getUnsafe();
public class AtomicIntegerDemo {
    // 请求总数
    public static int clientTotal = 5000;

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

    //java.util.concurrent.atomic.AtomicInteger;
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Semaphore和CountDownLatch模拟并发
        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) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("count:{"+count.get()+"}");
    }

    public static void add() {
        count.incrementAndGet();
    }
}

输出结果:

count:{5000}

AtomicLong的实现原理和AtomicInteger一致,只不过一个针对的是long变量,一个针对的是int变量。 而boolean变量的更新类AtomicBoolean类是怎样实现更新的呢?核心方法是compareAndSet()方法,其源码如下:

public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;
    int u = update ? 1 : 0;
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

可以看出,compareAndSet方法的实际上也是先转换成0,1的整型变量, 然后是通过针对int型变量的原子更新方法compareAndSwapInt来实现的。 可以看出atomic包中只提供了对boolean,int ,long这三种基本类型的原子更新的方法, 参考对boolean更新的方式,原子更新char,doule,float也可以采用类似的思路进行实现。

原子更新数组

atomic包下提供能原子更新数组中元素的类有:

AtomicIntegerArray //原子更新整型数组中的元素

AtomicLongArray //原子更新长整型数组中的元素

AtomicReferenceArray //原子更新引用类型数组中的元素

这几个类的用法一致,就以AtomicIntegerArray来总结下常用的方法:

getAndAdd(int i, int delta) //以原子更新的方式将数组中索引为i的元素与输入值相加

getAndIncrement(int i) //以原子更新的方式将数组中索引为i的元素自增加1

compareAndSet(int i, int expect, int update) //将数组中索引为i的位置的元素进行更新

可以看出,AtomicIntegerArray与AtomicInteger的方法基本一致, 只不过在AtomicIntegerArray的方法中会多一个指定数组索引位i。

public class AtomicIntegerArrayDemo {
    private static int[] value = new int[]{1, 2, 3};
    private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        //对数组中索引为1的位置的元素加5
        int result = integerArray.getAndAdd(1, 5);
        System.out.println(integerArray.get(1));
        System.out.println(result);
    }
}

输出结果:

7
2

原子更新引用类型

如果需要原子更新引用类型变量的话,为了保证线程安全,atomic也提供了相关的类:

AtomicReference //原子更新引用类型

AtomicReferenceFieldUpdater //原子更新引用类型里的字段

AtomicMarkableReference //原子更新带有标记位的引用类型

这几个类的使用方法也是基本一样的,以AtomicReference为例。

public class AtomicReferenceDemo {

    private static AtomicReference<User> reference = new AtomicReference<>();

    public static void main(String[] args) {
        User user1 = new User("a", 1);
        reference.set(user1);
        User user2 = new User("b",2);
        User user = reference.getAndSet(user2);
        System.out.println(user);
        System.out.println(reference.get());
    }

    static class User {
        private String userName;
        private int age;

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

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

输出结果:

User{userName='a', age=1}
User{userName='b', age=2}

首先将对象User1用AtomicReference进行封装,然后调用getAndSet方法, 从结果可以看出,该方法会原子更新引用的user对象, 变为User{userName='b', age=2},返回的是原来的user对象User{userName='a', age=1}。

原子更新字段类型

如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic同样也提供了相应的原子操作类:

AtomicIntegeFieldUpdater //原子更新整型字段类

AtomicLongFieldUpdater //原子更新长整型字段类

AtomicStampedReference //原子更新引用类型,这种更新方式会带有版本号。
// 而为什么在更新的时候会带有版本号,是为了解决CAS的ABA问题;

要想使用原子更新字段需要两步操作:

  • 原子更新字段类都是抽象类,只能通过静态方法newUpdater来创建一个更新器,并且需要设置想要更新的类和属性

  • 更新类的属性必须使用public volatile进行修饰

这几个类提供的方法基本一致,以AtomicIntegerFieldUpdater为例。

public class AtomicIntegerFieldUpdaterDemo {
    private static AtomicIntegerFieldUpdater updater =
        AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
    
    public static void main(String[] args) {
        User user = new User("a", 1);
        int oldValue = updater.getAndAdd(user, 5);
        System.out.println(oldValue);
        System.out.println(updater.get(user));
    }

    static class User {
        private String userName;
        public volatile int age;

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

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

输出结果:

1
6

创建AtomicIntegerFieldUpdater是通过它提供的静态方法进行创建, getAndAdd方法会将指定的字段加上输入的值,并且返回相加之前的值。 user对象中age字段原值为1,加5之后,可以看出user对象中的age字段的值已经变成了6。


免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。
传送门: mp.weixin.qq.com/s/Jzdd


Guess you like

Origin blog.51cto.com/13672983/2401479