Atomic integer, reference, array, updater - JUC - Concurrent Programming (Java)

Keywords: Concurrent Programming JUC

1, Atomic integer

JUC also provided:

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong

Let's take AtomicInteger as an example to understand the common API s. All operations are atomic.

  • Self increasing:
    • incrementAndGet(): Auto increment and get, equivalent to + + i
    • getAndIncrement(): get and auto increment, equivalent to i++
  • Self subtraction
    • decrementAndGet(): self subtraction and acquisition, equivalent to – i
    • getAndDecrement(): obtain and self subtract, equivalent to i –
  • Addition and subtraction method
    • getAndAdd(int delta): get and add Delta
    • addAndGet(int delta): add Delta and get

You can use the above API to check our previous withdrawal cases( Portal )Further modifications are as follows:

// Change only the withraw () method
// Before withraw ()
@Override
public void withdraw(Integer amount) {
    while (true) {
        int prev = balance.get();
        int next = prev - amount;
        if (balance.compareAndSet(prev, next)) {
        	break;
        }
    }
}
    
// Modified withraw
@Override
public void withdraw(Integer amount) {
	balance.getAndAdd(amount);
}

Obviously, the transformation can achieve the same function, but it should be more concise.

  • Complex operation

    • updateAndGet(IntUnaryOperator updateFunction): update and get

    • getAndUpdate(IntUnaryOperator updateFunction): go back and update

    IntUnaryOperator updateFunction: what parameter is this?

    @FunctionalInterface
    public interface IntUnaryOperator {
    
        /**
         * Applies this operator to the given operand.
         *
         * @param operand the operand
         * @return the operator result
         */
        int applyAsInt(int operand);
        //...
    }
    

    By looking at the source code, we find that this is a functional interface. For example, we do a simple multiplication operation:

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class TestAtmoticInteger {
        public static void main(String[] args) {
            AtomicInteger ai = new AtomicInteger(10);
            ai.updateAndGet(n ->  n * 10);
            System.out.println(ai.get());
        }
    }
    
    // test result
    100
    

    Is it very simple to use, so how is it implemented? The following is a brief introduction to the principle. We simulate updateAndGet through while loop + compareAndSet to realize multiplication. The code is as follows:

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class TestAtmoticInteger {
        public static void main(String[] args) {
            AtomicInteger ai = new AtomicInteger(10);
    //        ai.updateAndGet(n ->  n * 10);
    //        System.out.println(ai.get());
    
            while (true) {
                int prev = ai.get();
                int next = prev * 10;
                if (ai.compareAndSet(prev, next)) {
                    break;
                }
            }
            System.out.println(ai.get());
       
    

    The above can also achieve the same effect. Let's see how the source code is implemented

    /**
         * Atomically updates the current value with the results of
         * applying the given function, returning the updated value. The
         * function should be side-effect-free, since it may be re-applied
         * when attempted updates fail due to contention among threads.
         *
         * @param updateFunction a side-effect-free function
         * @return the updated value
         * @since 1.8
         */
        public final int updateAndGet(IntUnaryOperator updateFunction) {
            int prev, next;
            do {
                prev = get();
                next = updateFunction.applyAsInt(prev);
            } while (!compareAndSet(prev, next));
            return next;
        }
    

    The source code is also implemented in this way.

2, Atomic reference

1. AtomicReference - Atomic reference type

Why do you need atomic references? Sometimes we need to modify not only the value of the variable, but also the value of the member variable of the reference type. Atomic integers are not applicable here, so there is an atomic reference.

In the previous withdrawal cases, we all know that in real life, the withdrawal amount can be decimal. For novels involving the amount, we generally use BigDecimal. The code is as follows:

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * Account interface
 */
public interface AccountDecimal {
    // Get balance
    BigDecimal getBalance();
    // withdraw money
    void withdraw(BigDecimal amount);
    // Simulate multiple thread withdrawal operations
    static void multiWithdraw(AccountDecimal account) {
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(BigDecimal.TEN);
            }));
        }
        long start = System.currentTimeMillis();
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.currentTimeMillis();
        System.out.println("balance: "+ account.getBalance() + " cost: " + (end - start) + " ms");
    }
}


import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * CAS Lock free implementation
 */
public class AccountDecimalCAS implements AccountDecimal {

    private final AtomicReference<BigDecimal> balance;

    public AccountDecimalCAS(BigDecimal balance) {
        this.balance = new AtomicReference<>(balance);
    }

    @Override
    public BigDecimal getBalance() {
        return balance.get();
    }

    @Override
    public void withdraw(BigDecimal amount) {
        balance.getAndUpdate(m -> m.subtract(amount));
    }

}

import java.math.BigDecimal;

/**
 * Test class
 */
public class TestAccount {
    public static void main(String[] args) {

        AccountDecimal account3 = new AccountDecimalCAS(new BigDecimal("10000"));
        AccountDecimal.multiWithdraw(account3);

    }
}

// test result
balance: 0 cost: 67 ms
  • Note: we use 1000 threads here, which is not in line with the CAS usage scenario, but we only do so for testing, which is not suitable in actual use.

2. AtomicStampedReference - Atomic reference type (version tag)

The ABA problem in atomic reference, that is, the initial value is changed from a to B and then changed to A. can we judge whether the change amount has been modified?

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j(topic = "c.TestABA")
public class TestABA {
    static AtomicReference<String> ref = new AtomicReference<>("A");
    public static void main(String[] args) throws InterruptedException {
        log.debug("main start ...");
        // Get value A
        String prev = ref.get();
        other();
        TimeUnit.SECONDS.sleep(1);
        // Try changing to C
        log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
    }

    private static void other() {
        new Thread(() -> {
            log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
        }, "t1").start();

        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
        }, "t2").start();
    }
}

// test result
2021-09-27 10:44:52.619 DEBUG [main] c.TestABA - main start ...
2021-09-27 10:44:52.662 DEBUG [t1] c.TestABA - change A->B true
2021-09-27 10:44:53.161 DEBUG [t2] c.TestABA - change B->A true
2021-09-27 10:44:54.161 DEBUG [main] c.TestABA - change A->C true

A has been modified to C successfully, but we can't judge whether a has been modified (others have been modified). Of course, in most cases, ABA problems do not affect business, nor do they rule out situations that will have an impact. When there is a need for this scenario - once the shared variable is modified, other threads can perceive it. What should we do? You need to use the AtomicStampedReference class, or use the above small case to understand the use of AtomicStampedReference.

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

@Slf4j(topic = "c.TestABA")
public class TestABAStamped {
    static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
    public static void main(String[] args) throws InterruptedException {
        log.debug("main start ...");
        // Get value A
        String prev = ref.getReference();
        int stamp = ref.getStamp();
        other();
        TimeUnit.SECONDS.sleep(1);
        // Try changing to C
        log.debug("change A->C {},stamp {}->{}", ref.compareAndSet(prev, "C", stamp, stamp + 1), stamp, stamp + 1);

    }

    private static void other() {
        new Thread(() -> {
            int stamp = ref.getStamp();
            log.debug("change A->B {},stamp {}->{}", ref.compareAndSet(ref.getReference(), "B", stamp, stamp + 1), stamp, stamp + 1);
        }, "t2").start();

        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            int stamp = ref.getStamp();
            log.debug("change B->A {},stamp {}->{}", ref.compareAndSet(ref.getReference(), "A", stamp, stamp + 1), stamp, stamp + 1);
        }, "t2").start();
    }
}

// test result
2021-09-27 10:57:45.997 DEBUG [main] c.TestABA - main start ...
2021-09-27 10:57:46.039 DEBUG [t2] c.TestABA - change A->B true,stamp 0->1
2021-09-27 10:57:46.539 DEBUG [t2] c.TestABA - change B->A true,stamp 1->2
2021-09-27 10:57:47.539 DEBUG [main] c.TestABA - change A->C false,stamp 0->1

When returning to the main thread, the current version is still 0, while other threads have changed the version to 2. Although the values are all A, the final modification fails because the versions are inconsistent, which solves the ABA problem.

3. AtomicMarkableReference - Atomic reference type (Boolean tag)

AtomicStampedReference can add a version number to an atomic reference to track the entire change process of the atomic reference; However, sometimes we don't care how many times the reference variable has been changed, just whether it has been changed. Here we use the AtomicMarkableReference class.

  • AtomicStampedReference uses an integer internally to record the version number
  • AtomicMarkableReference: internally uses Boolean values to record whether changes have been made

Scene Description: when there is garbage at home, we will throw it into the garbage bag in the garbage can. If the garbage bag is full, we need to replace it with a new empty garbage bag; Assuming that the garbage bag is full now, we need to replace it with a new one. How can we achieve it?

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * disposable bag
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GarbageBag {
    private String desc;
}

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;

/**
 * Replace the garbage bag
 */
@Slf4j(topic = "c.ReplaceGarbageBag")
public class ReplaceGarbageBag {
    public static void main(String[] args) throws InterruptedException {
        GarbageBag bag = new GarbageBag("Full garbage bag");
        // true indicates that the garbage bag is full and false is empty
        AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
        log.debug("start ...");
        GarbageBag prev = ref.getReference();
        log.debug(prev.toString());
        TimeUnit.SECONDS.sleep(1);
        log.debug("Change an empty garbage bag?");
        boolean success = ref.compareAndSet(prev, new GarbageBag("Empty garbage bag"), true, false);
        log.debug("Have you replaced it?{}", success);
        log.debug(ref.getReference().toString());
    }
}

// test result
2021-09-27 11:15:49.322 DEBUG [main] c.ReplaceGarbageBag - start ...
2021-09-27 11:15:49.324 DEBUG [main] c.ReplaceGarbageBag - GarbageBag(desc=Full garbage bag)
2021-09-27 11:15:50.325 DEBUG [main] c.ReplaceGarbageBag - Change an empty garbage bag?
2021-09-27 11:15:50.325 DEBUG [main] c.ReplaceGarbageBag - Have you replaced it? true
2021-09-27 11:15:50.326 DEBUG [main] c.ReplaceGarbageBag - GarbageBag(desc=Empty garbage bag)

3, Atomic array

Sometimes we need thread safety to include elements in the array. At this time, we need to use classes related to atomic arrays.

  • AtomicIntegerArray: atomic shaping array
  • AtomicLongArray: atomic length shaping array
  • AtomicReferenceArray: atomic reference array

Let's test the thread safety of ordinary arrays and atomic arrays through the code

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class TestArray {
    public static void main(String[] args) {
        // Normal array
        demo(
                () -> new int[10],
                array ->array.length,
                (array, index) -> array[index]++,
                array -> System.out.println(Arrays.toString(array))
        );
        // Atomic array
        demo(
                () -> new AtomicIntegerArray(10),
                AtomicIntegerArray::length,
                AtomicIntegerArray::getAndIncrement,
                System.out::println
        );
    }
    /**
     Parameter 1, provides an array, which can be a thread unsafe array or a thread safe array
     Parameter 2, method to get the length of the array
     Parameter 3: Auto increment method, backhaul array, index
     Parameter 4, method of printing array
     */
    // supplier provider makes something out of nothing () - > result
    // Function function one parameter one result (parameter) - > result, BiFunction (parameter 1, parameter 2) - > result
    // Consumer consumer has no result for a parameter (parameter) - > void, biconsumer (parameter 1, parameter 2) - >
    private static <T> void demo(
            Supplier<T> arraySupplier,
            Function<T, Integer> lengthFun,
            BiConsumer<T, Integer> putConsumer,
            Consumer<T> printConsumer
    ) {
        List<Thread> list = new ArrayList<>();
        T array = arraySupplier.get();
        int len = lengthFun.apply(array);
        for (int i = 0; i < len; i++) {
            list.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    putConsumer.accept(array, j % len);
                }
            }));
        }
        list.forEach(Thread::start);
        list.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        printConsumer.accept(array);
    }
}

// test result
[8910, 8911, 8901, 8907, 8871, 8893, 8879, 8876, 8883, 8882]
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

Note: several in the code are functional interfaces and lambda expressions in the new features of JDK8. Those who are interested or do not understand can consult relevant documents by themselves, which will not be detailed here.

4, Atomic Updater

There are three types of atomic updaters:

  • AtomicReferenceFieldUpdater
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater

Using the atomic updater, you can perform atomic operations on a Field (Field field) of the object. It can only be used with volatile modified fields, otherwise an error is reported: Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

@Slf4j(topic = "c.TestAtomicFieldUpdater")
public class TestAtomicFieldUpdater {
    public static void main(String[] args) {
        Student stu = new Student();
        AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
        System.out.println(updater.compareAndSet(stu, null, "Zhang San"));
        System.out.println(updater.get(stu));
    }
}


class Student {
    volatile String name;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

QQ:806797785

Warehouse address: https://gitee.com/gaogzhen/concurrent

Posted by metalblend on Sun, 26 Sep 2021 20:22:29 -0700