Multithreaded learning four

Keywords: Java Netty Microservices

Navigation

1,Multithreaded learning one

6. No lock of shared model

In this chapter, we will implement concurrency control through non blocking optimistic locks

6.1 problem raising

There are the following requirements to ensure the thread safety of the account.withdraw withdrawal method Test5.java

public class Test5 {

    public static void main(String[] args) {
        Account.demo(new AccountUnsafe(10000));
    }
}

class AccountUnsafe implements Account {
    private Integer balance;
    public AccountUnsafe(Integer balance) {
        this.balance = balance;
    }
    @Override
    public Integer getBalance() {
        return balance;
    }
    @Override
    
    public void  withdraw(Integer amount) {
        // Thread safety can be achieved by locking here. If not, the result will be abnormal
        synchronized (this){
            balance -= amount;
        }
    }
}


interface Account {
    // Get balance
    Integer getBalance();
    // withdraw money
    void withdraw(Integer amount);
    /**
     * 1000 threads will be started in the method, and each thread will do the operation of - 10 yuan
     * If the initial balance is 10000, the correct result should be 0
     */
    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance()
                + " cost: " + (end-start)/1000_000 + " ms");
    }
}

Solution - no lock

In the above code, the synchronized locking operation can be used to achieve thread safety, but the synchronized locking operation consumes too much resources. Here we use no lock to solve this problem: Test5.java

class AccountSafe implements Account{

    AtomicInteger atomicInteger ;
    
    public AccountSafe(Integer balance){
        this.atomicInteger =  new AtomicInteger(balance);
    }
    
    @Override
    public Integer getBalance() {
        return atomicInteger.get();
    }

    @Override
    public void withdraw(Integer amount) {
        // Core code
        while (true){
            int pre = getBalance();
            int next = pre - amount;
            if (atomicInteger.compareAndSet(pre,next)){
                break;
            }
        }
        // It can be simplified to the following method
        // balance.addAndGet(-1 * amount);
    }
}

6.2 CAS and volatile

cas

The AtomicInteger solution seen earlier does not use locks internally to protect the thread safety of shared variables. So how is it implemented?

    @Override
    public void withdraw(Integer amount) {
        // Core code
        // You need to keep trying until you succeed
        while (true){
            // For example, I got the old value of 1000
            int pre = getBalance();
            // On this basis, 1000-10 = 990
            int next = pre - amount;
            /*
             compareAndSet This is exactly the check. Before set ting, compare prev with the current value
             - Inconsistent. next is voided. If false is returned, it means failure
             For example, other threads have subtracted, and the current value has been reduced to 990
             Then the 990 of this thread will be invalidated. Enter while and retry the next cycle
             - Consistent, set next as the new value, and return true to indicate success
			 */
            if (atomicInteger.compareAndSet(pre,next)){
                break;
            }
        }
    }

The key is compareAndSet, which is abbreviated as CAS (also known as Compare And Swap). It must be atomic operation.

volatile

In the AtomicInteger in the above code, the value attribute that holds the value uses volatile. When obtaining a shared variable, in order to ensure the visibility of the variable, you need to use volatile decoration.

It can be used to modify member variables and static member variables. It can prevent threads from finding the value of variables from their own work cache and must get them from main memory
Its value, thread operation and volatile variable are all direct operations on main memory. That is, the modification of volatile variable by one thread is visible to another thread.

One more word
volatile only ensures the visibility of shared variables so that other threads can see the latest value, but it can not solve the problem of instruction interleaving (the original value cannot be guaranteed)
(child)

CAS must use volatile to read the latest value of shared variables to achieve the effect of [compare and exchange]

Why is lockless efficiency high

  1. In the case of no lock, even if the retry fails, the thread will always run at high speed without stopping, and synchronized will cause the thread to switch context and enter blocking when it does not obtain the lock. Let's make an analogy: threads are like a racing car on a high-speed track. When running at high speed, the speed is very fast. Once a context switch occurs, it's like a racing car needs to slow down and shut down. When it is awakened, it has to fire, start and accelerate again... It's expensive to return to high-speed operation
  2. However, in the case of no lock, because the thread needs the support of additional CPU to keep running, the CPU here is like a high-speed runway. Without an additional runway, it is impossible for the thread to run at high speed. Although it will not enter the blocking, it will still enter the runnable state due to the lack of time slice, which will still lead to context switching.

Characteristics of CAS

Combined with CAS and volatile, lock free concurrency can be realized, which is suitable for scenarios with few threads and multi-core CPU s.

  1. CAS is based on the idea of optimistic locking: the most optimistic estimate is that you are not afraid of other threads to modify shared variables. Even if you do, it doesn't matter. I'll try again at a loss.
  2. synchronized is based on the idea of pessimistic lock: the most pessimistic estimate is to prevent other threads from modifying shared variables. When I lock, you don't want to change it. Only after I change the lock can you have a chance.
  3. CAS embodies lock free concurrency and non blocking concurrency. Please understand the meaning of these two sentences carefully
    1. Because synchronized is not used, the thread will not be blocked, which is one of the factors to improve efficiency
    2. However, if the competition is fierce (there are many write operations), it is conceivable that retries must occur frequently, but the efficiency will be affected

6.3 atomic integers

java.util.concurrent.atomic also provides some concurrent tool classes, which are divided into five categories:

  1. Update base types atomically

    • AtomicInteger: integer atomic class
    • AtomicLong: long integer atomic class
    • AtomicBoolean: Boolean atomic class

    The methods provided by the above three classes are almost the same, so we will take AtomicInteger as an example.

  2. Atomic reference

  3. Atomic array

  4. Field Updater

  5. Atomic accumulator

Let's first discuss the atomic integer class and take AtomicInteger as an example to discuss its api interface: by observing the source code, we can find that AtomicInteger is implemented internally through the principle of cas!! Seems to have found a new world! Test6.java

    public static void main(String[] args) {
        AtomicInteger i = new AtomicInteger(0);
        // Get and auto increment (i = 0, result i = 1, return 0), similar to i++
        System.out.println(i.getAndIncrement());
        // Auto increment and get (i = 1, result i = 2, return 2), similar to + + i
        System.out.println(i.incrementAndGet());
        // Subtract and get (i = 2, result i = 1, return 1), similar to -- i
        System.out.println(i.decrementAndGet());
        // Gets and subtracts itself (i = 1, result i = 0, returns 1), similar to i--
        System.out.println(i.getAndDecrement());
        // Get and add value (i = 0, result i = 5, return 0)
        System.out.println(i.getAndAdd(5));
        // Add value and get (i = 5, result i = 0, return 0)
        System.out.println(i.addAndGet(-5));
        // Get and update (i = 0, p is the current value of i, result i = -2, return 0)
        // Functional programming interface, in which the operation in the function can guarantee the atom, but the function needs no side effects
        System.out.println(i.getAndUpdate(p -> p - 2));
        // Update and get (i = -2, p is the current value of i, result i = 0, return 0)
        // Functional programming interface, in which the operation in the function can guarantee the atom, but the function needs no side effects
        System.out.println(i.updateAndGet(p -> p + 2));
        // Get and calculate (i = 0, p is the current value of i, x is parameter 1, the result i = 10, returns 0)
        // Functional programming interface, in which the operation in the function can guarantee the atom, but the function needs no side effects
        // If getAndUpdate references an external local variable in lambda, ensure that the local variable is final
        // getAndAccumulate can reference an external local variable through parameter 1, but it does not have to be final because it is not in lambda
        System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
        // Calculate and obtain (i = 10, p is the current value of i, x is the value of parameter 1, the result i = 0, return 0)
        // Functional programming interface, in which the operation in the function can guarantee the atom, but the function needs no side effects
        System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));
    }
    

6.4 atomic references

Why do you need atomic reference types? Ensure that shared variables of reference type are thread safe (ensure that this atomic reference has not referenced others).

A basic type atomic class can only update one variable. If you need to update multiple variables, you need to use a reference type atomic class.

  • AtomicReference: reference type atomic class
  • AtomicStampedReference: atomic updates reference types with version numbers. This class associates integer values with references, which can be used to solve the update data and version number of atomic data, and solve the ABA problem that may occur when atomic updates are performed using CAS.
  • AtomicMarkableReference: updates reference types with tags. This class associates boolean tags with references, and can also solve the ABA problem that may occur when atomic updates are performed using CAS.

Thread safety of BigDecimal deposit and withdrawal using atomic reference: Test7.java

The following is an unsafe implementation process:

class DecimalAccountUnsafe implements DecimalAccount {
    BigDecimal balance;
    public DecimalAccountUnsafe(BigDecimal balance) {
        this.balance = balance;
    }
    @Override
    public BigDecimal getBalance() {
        return balance;
    }
    @Override
    public void withdraw(BigDecimal amount) {
        BigDecimal balance = this.getBalance();
        this.balance = balance.subtract(amount);
    }
}

The solution code is as follows: in the AtomicReference class, there is a variable of type value to save the reference to the BigDecimal object.

class DecimalAccountCas implements DecimalAccount{

    //private BigDecimal balance;
    private AtomicReference<BigDecimal> balance ;

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

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

    @Override
    public void withdraw(BigDecimal amount) {
        while(true){
            BigDecimal pre = balance.get();
            // Note: balance here returns a new object, pre= next
            BigDecimal next = pre.subtract(amount);
            if (balance.compareAndSet(pre,next)){
                break;
            }
        }
    }
}

ABA problems and Solutions

ABA problem: Test8.java is shown in the following program. Although two threads in the other method modify the shared variable, it becomes the original value after modification, which is invisible in the main thread. This operation has no impact on the business code.

    static AtomicReference<String> ref = new AtomicReference<>("A");
    public static void main(String[] args) throws InterruptedException {
        log.debug("main start...");
        // Get value A
        // This shared variable is modified by its thread
        String prev = ref.get();
        other();
        utils.sleep(1);
        // Try to change 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();
        utils.sleep(1);
        new Thread(() -> {
            // Note: if log. Debug ("change B - > a {}", ref.compareandset (ref.get(), new string ("a")) is used here;
            // Log. Debug ("change a - > C {}", ref.compareandset (prev, "C");
            // false is printed because the reference of the object returned by new String("A") is different from that of the object returned by "A"!
            log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
        }, "t2").start();
    }

The main thread can only judge whether the value of the shared variable is the same as the initial value a, and cannot perceive the situation of changing from a to B and back to A. if the main thread wants to: as long as other threads [have moved] the shared variable, its cas will fail. At this time, it is not enough to compare the value, and it needs to add another version number. Use AtomicStampedReference to solve the problem.

AtomicStampedReference

Solving ABA problem Test9.java

static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A",0);
    public static void main(String[] args) throws InterruptedException {
        log.debug("main start...");
        // Get value A
        int stamp = ref.getStamp();
        log.info("{}",stamp);
        String prev = ref.getReference();
        other();
        utils.sleep(1);
        // Try to change to C
        log.debug("change A->C {}", ref.compareAndSet(prev, "C",stamp,stamp+1));
    }
    
    private static void other() {
        new Thread(() -> {
            int stamp = ref.getStamp();
            log.info("{}",stamp);
            log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",stamp,stamp+1));
        }, "t1").start();
        utils.sleep(1);
        new Thread(() -> {
            int stamp = ref.getStamp();
            log.info("{}",stamp);
            log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",stamp,stamp+1));
        }, "t2").start();
    }

AtomicMarkableReference

AtomicStampedReference can add the version number to the atomic reference and track the whole change process of atomic reference, such as a - > b - > A - > C. through AtomicStampedReference, we can know that the reference variable has been changed several times in the middle. However, sometimes, I don't care about how many times the reference variable has been changed, but just whether it has been changed. Therefore, AtomicMarkableReference Test10.java is available

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (IMG ncacipbf-1637992032581) (assets / 1594803309714. PNG)]

6.5 atomic array

Update an element in an array in an atomic way

  • AtomicIntegerArray: integer array atomic class
  • AtomicLongArray: long integer array atomic class
  • AtomicReferenceArray: reference type array atomic class

The methods provided by the above three classes are almost the same, so let's take AtomicIntegerArray as an example. Example code: Test11.java

We will use functional programming to implement it. First, take a look at the javadoc documents of some functional programming interfaces

Represents a supplier of results.
express supplier Results.
There is no requirement that a new or distinct result be returned each time the supplier is invoked.
It is not required to return a new or different result each time the supplier is called.
This is a functional interface whose functional method is get().
This is a function interface whose function method is get(). 
public interface Supplier<T> {
    /**
     * Gets a result.
     * @return a result
     */
    T get();
}

Represents a function that accepts one argument and produces a result.
Represents a function that accepts a parameter and generates a result.
This is a functional interface whose functional method is apply(Object).
This is a function interface whose function method is apply(Object). 
public interface Function<T, R> {
/**
* Applies this function to the given argument.
* @param t the function argument
* @return the function result
*/	
  R apply(T t);
  //....
  }
  
  Represents an operation that accepts two input arguments and returns no result. This is the two-arity specialization of Consumer. Unlike most other functional interfaces, BiConsumer is expected to operate via side-effects.
Represents an operation that accepts two input parameters and does not return a result. This is it. Consumer Binary parameter version of. Unlike most other functional interfaces, BiConsumer Expect to perform operations with side effects.
This is a functional interface whose functional method is accept(Object, Object).
This is a function interface whose function method is accept(Object,Object). 
public interface BiConsumer<T, U> {
   void accept(T t, U u);
     //....
  }



Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.
Represents an operation that accepts a single input parameter but does not return a result. Unlike most other functional interfaces, consumers expect to perform operations with side effects. 
public interface Consumer<T> {
void accept(T t);
   //....
  }

6.6 field Updater

AtomicReferenceFieldUpdater / / domain field, AtomicIntegerFieldUpdater, AtomicLongFieldUpdater

Note: using the Field updater, you can perform atomic operations on a Field of the object. It can only be used with volatile modified fields, otherwise the exception Test12.java will appear

Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type

6.7 atomic accumulator

Accumulator performance comparison

Use of LongAdder accumulator

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            demo(() -> new LongAdder(), adder -> adder.increment());
        }
        for (int i = 0; i < 5; i++) {
            demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
        }

    }
    
    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
        T adder = adderSupplier.get();
        long start = System.nanoTime();
        List<Thread> ts = new ArrayList<>();
        // 4 threads, each accumulating 500000
        for (int i = 0; i < 40; i++) {
            ts.add(new Thread(() -> {
                for (int j = 0; j < 500000; j++) {
                    action.accept(adder);
                }
            }));
        }
        ts.forEach(t -> t.start());
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(adder + " cost:" + (end - start)/1000_000);
    }

The reason for performance improvement is very simple. When there is competition, set multiple accumulation units (but not exceeding the number of cpu cores), Therad-0 accumulates Cell[0], and Thread-1 accumulates Cell[1]... Finally summarize the results. In this way, they operate on different Cell variables when accumulating, thus reducing CAS retry failures and improving performance.

LongAdder of source code

The LongAdder class has several key fields

// Accumulation cell array, lazy initialization
transient volatile Cell[] cells;
// Base value. If there is no competition, cas is used to accumulate this field
transient volatile long base;
// When cells are created or expanded, it is set to 1, indicating locking
transient volatile int cellsBusy;

cas lock

Using cas to implement a spin lock

// Do not use in production practice!!!
public class LockCas {
    private AtomicInteger state = new AtomicInteger(0);
    public void lock() {
        while (true) {
            if (state.compareAndSet(0, 1)) {
                break;
            }
        }
    }
    public void unlock() {
        log.debug("unlock...");
        state.set(0);
    }
}

test

        LockCas lock = new LockCas();
        new Thread(() -> {
            log.debug("begin...");
            lock.lock();
            try {
                log.debug("lock...");
                sleep(1);
            } finally {
                lock.unlock();
            }
        }).start();
        new Thread(() -> {
            log.debug("begin...");
            lock.lock();
            try {
                log.debug("lock...");
            } finally {
                lock.unlock();
            }
        }).start();

Principle of pseudo sharing

Where Cell is the accumulation unit

// Prevent cache line pseudo sharing
@sun.misc.Contended
static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
    // The most important method is used for cas accumulation. prev represents the old value and next represents the new value
    final boolean cas(long prev, long next) {
        return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);
    }
    // Omit unimportant code
}

The significance of the @ sun.misc.contented annotation is discussed below

Let's start with cache. The speed of cache and memory is compared

Because the speed of cpu and memory is very different, we need to read data to cache to improve efficiency. The closer the cache is to the cpu, the faster it will be.
The cache is based on the cache behavior unit, and each cache line corresponds to a piece of memory, generally 64 byte s (8 long). The addition of cache will cause the generation of data copies, that is, the same data will be cached in cache lines of different cores. The CPU should ensure the consistency of data. If a CPU core changes data, the whole cache line corresponding to other CPU cores must be invalidated.

Because cells are in the form of arrays and are stored continuously in memory, a Cell is 24 bytes (16 bytes of object header and 8 bytes of value)
This cache line can store 2 Cell objects. Here comes the question:
Core-0 to modify Cell[0], Core-1 to modify Cell[1]

No matter who modifies it successfully, the cache lines of the other Core will become invalid. For example, cell [0] = 6000 and cell [1] = 8000 in Core-0 should be accumulated
Cell[0]=6001, Cell[1]=8000. In this case, the cache line of Core-1 will be invalidated, @ sun.misc.contented is used to solve this problem. Its principle is to add 128 bytes of padding before and after the object or field using this annotation, so that the CPU can occupy different cache lines when pre reading the object to the cache. In this way, the cache line of the other party will not be invalidated

Let's take a look at the accumulation increment() method of the LongAdder class, which mainly calls the following methods

  public void add(long x) {
        // as is the accumulation cell array
        // b is the base value
        // x is the cumulative value
        Cell[] as; long b, v; int m; Cell a;
        // Two conditions for entering if
        // 1. as has a value, which indicates that competition has occurred and enters if
        // 2. cas fails to accumulate base, which indicates that the base competes and enters if
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            // Uncompleted indicates that the cell has no competition
            boolean uncontended = true;
            if (
                // as has not been created
                    as == null || (m = as.length - 1) < 0 ||
                            // The cell corresponding to the current thread has not been created. a is the cell of the current thread
                            (a = as[getProbe() & m]) == null ||
       // Failed to accumulate the cells of the current thread. Uncontended = false (a is the cell of the current thread)
                            !(uncontended = a.cas(v = a.value, v + x))
            ) {
                // Enter the process of cell array creation and cell creation
                longAccumulate(x, null, uncontended);
            }
        }
    }

add method analysis

add flowchart

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-UwVAi6Ua-1637992032591)(assets/1594823466127.png)]

  final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
        // The current thread does not have a corresponding cell, so an h value needs to be randomly generated to bind the current thread to the cell
        if ((h = getProbe()) == 0) {
            // Initialize probe
            ThreadLocalRandom.current();
            // h corresponds to the new probe value, which is used to correspond to the cell
            h = getProbe();
            wasUncontended = true;
        }
        // If collapse is true, it means capacity expansion is required
        boolean collide = false;
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            // There are already cells
            if ((as = cells) != null && (n = as.length) > 0) {
                // However, there is no cell corresponding to the current thread
                if ((a = as[(n - 1) & h]) == null) {
                    // Lock cellsBusy and create a cell. The initial cumulative value of the cell is x
                    // break if successful, otherwise continue the continue cycle
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    // Judge that the slot is indeed empty
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                }
                // If there is competition, change the cell corresponding to the thread to retry the cas
                else if (!wasUncontended)
                    wasUncontended = true;
                    // cas tries to accumulate, fn with LongAccumulator is not null, and fn with LongAdder is null
                else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
                    break;
                    // If the length of the cells has exceeded the maximum length or has been expanded, change the cell corresponding to the thread to retry the cas
                else if (n >= NCPU || cells != as)
                    collide = false;
                    // Ensure that collapse is false. If you enter this branch, you will not enter the following else if for capacity expansion
                else if (!collide)
                    collide = true;
                    // Lock
                else if (cellsBusy == 0 && casCellsBusy()) {
                    // Successful locking, capacity expansion
                    continue;
                }
                // Change the cell corresponding to the thread
                h = advanceProbe(h);
            }
            // There are no cells yet. Cells = = as means that no other thread modifies cells. As and cells refer to the same object. Use casCellsBusy() to try to lock cellsBusy
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                // Lock successfully, initialize cells, with the initial length of 2, and fill in a cell
                // If successful, break;
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            // The previous two cases failed. Try to use casBase accumulation for base
            else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
                break;
        }
    }

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-LriO5n4z-1637992032592)(assets/1594826116135.png)]

The logic of the first else if code in the figure above is the processing logic when cells are not created.

The logic of the if code in the figure above includes two cases: the cell corresponding to the thread has been created and the cell has not been created.

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-4PIS2XWC-1637992032595)(assets/1594826821664.png)]

If the cell corresponding to the thread has not been created, the code in the first red box is executed. The logic is as follows

If the cell corresponding to the thread has been created, the code in the second red box is executed. The logic is as follows

sum method analysis

Get the final result. The sum method is used to add up the values of each accumulation unit to get the total result.

    public long sum() {
        Cell[] as = cells; Cell a;
        long sum = base;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

6.8 Unsafe

summary

Unsafe objects provide very low-level methods for operating memory and threads. Unsafe objects cannot be called directly, but can only be obtained through reflection. The park method of LockSupport and the underlying method of cas are implemented through the unsafe class. Test14.java

    static Unsafe unsafe;
    static {
        try {
            // Unsafe uses the singleton pattern. The unsafe object is a private variable in the class
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new Error(e);
        }
    }
    static Unsafe getUnsafe() {
        return unsafe;
    }

Unsafe CAS operation

  1. cas operation with Unsafe: Test15.java
  2. Use the custom AtomicData to implement the previous thread safe atomic integer, and use the previous withdrawal instance to verify Test16.java

6.9 summary

  1. CAS and volatile
  2. API under juc package
    1. Atomic integer
    2. Atomic reference
    3. Atomic array
    4. Field Updater
    5. Atomic accumulator
  3. Unsafe
  4. Principle aspect
    1. LongAdder source code
    2. Pseudo sharing

7. Immutability of shared model

7.1 date conversion

The problem is that the following code has a high probability of java.lang.NumberFormatException or incorrect date resolution results when running because SimpleDateFormat is not thread safe.

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    log.debug("{}", sdf.parse("1951-04-21"));
                } catch (Exception e) {
                    log.error("{}", e);
                }
            }).start();
        }

Idea - immutable object

If an object cannot modify its internal state (properties), it is thread safe, because there is no concurrent modification! Such objects are
There are many in Java. For example, after Java 8, a new date formatting class DateTimeFormatter is provided:

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LocalDate date = dtf.parse("2018-10-01", LocalDate::from);
                log.debug("{}", date);
            }).start();
        }

7.2 immutable design

  1. For more information about immutable classes, refer to this here
  2. final class knowledge, reference here

Another more familiar String class is immutable. Take it as an example to illustrate the elements of immutable class design

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    /** Cache the hash code for the string */
    private int hash; // Default to 0
    // ...
}

Use of final

It is found that all attributes in this class and class are final. The attribute is modified with final to ensure that the attribute is read-only and cannot be modified. The class is modified with final to ensure that the methods in this class cannot be overwritten to prevent subclasses from inadvertently destroying immutability.

defensive copy

However, some students will say that when using strings, there are also some methods related to modification, such as substring. Let's take a look at these methods
Take substring as an example:

    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        // The above is some verification, and the following is the real creation of a new String object
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

It is found that it calls the String construction method to create a new String, and then enter the construction to see whether to make a change to the final char[] value
Modified: it is found that there is no change. When constructing a new string object, a new char[] value will be generated to copy the content. This method of avoiding sharing by creating replica objects is called [protective copy]

    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        // The above is some security verification. The following is to assign a value to the String object. A new array is created to save the value of the String object
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

Mode sharing element

  1. Introduction definition English Name: Flyweight pattern. When a limited number of objects of the same type need to be reused

  2. reflect

    1. In JDK, wrapper classes such as Boolean, Byte, Short, Integer, Long, and Character provide valueOf methods. For example, valueOf of Long caches Long objects between - 128 and 127. Objects will be reused in this range. If the range is greater than this, Long objects will be created:

      public static Long valueOf(long l) {
       final int offset = 128;
       if (l >= -128 && l <= 127) { // will cache
       return LongCache.cache[(int)l + offset];
       }
       return new Long(l);
      }
      
      

      be careful:
      The range of byte, short and long caches is - 128 ~ 127
      The range of Character cache is 0 ~ 127
      The default range of Integer is - 128 ~ 127. The minimum value cannot be changed, but the maximum value can be changed by adjusting the virtual machine parameter "- Djava.lang.Integer.IntegerCache.high"
      Boolean caches TRUE and FALSE

    2. String string pool

    3. BigDecimal BigInteger

  3. diy: for example, an online mall application has thousands of QPS. If the database connection is re created and closed every time, the performance will be greatly affected. At this time, create a batch of connections in advance and put them into the connection pool. After a request arrives, the connection is obtained from the connection pool and returned to the connection pool after use, which not only saves the creation and closing time of the connection, but also realizes the reuse of the connection, so as not to let the huge number of connections crush the database. Test17.java

Principle of final

  1. How to set the final variable

    1. After understanding the volatile principle, it is easier to compare the implementation of final

      public class TestFinal {final int a=20;}
      

      Bytecode

      0: aload_0
      1: invokespecial #1 // Method java/lang/Object."<init>":()V
      4: aload_0
      5: bipush 20
      7: putfield #2 // Field a:I
       <-- Write barrier
      10: return
      
      
    2. The assignment of the final variable must be initialized at the time of definition or in the constructor. It is found that the assignment of the final variable will also be completed through the putfield instruction. Similarly, a write barrier will be added after this instruction to ensure that it will not appear as 0 when other threads read its value.

  2. The principle of obtaining final variable: understand it from the level of bytecode video.

7.3 summary of this chapter

  1. Use of immutable classes
  2. Immutable class design
  3. Principle: final
  4. Mode aspect
    1. Meta mode - > set thread pool

problem

  1. When will it lead to the transition from user state to kernel state? When locking in synchronized.
  2. How does final optimize the read speed? After reviewing the jvm, you can understand it. video

Posted by BigMonkey on Tue, 30 Nov 2021 15:56:11 -0800