juc--Summary of the core problems of concurrent programming ③

Continued from the previous article juc – Summary of the core issues of concurrent programming ②

1. Asynchronous callback

1. What is asynchronous callback

The most common one we usually use is synchronous callback. Synchronous callback will block, and a single thread needs to wait for the return of the result to continue execution.
insert image description here

Suppose there are two tasks A and B. Task A needs to use task B to calculate the results. There are two ways to achieve it:

  1. A and B execute sequentially in the same thread. That is, execute B first, and then execute A after the return result is obtained.

  2. A and B execute in parallel. When A needs the calculation result of B, if B has not finished executing, A can do other work first to avoid blocking, and ask B again after a period of time.

We can directly write a method in A to process the result processed by B, and then call the method A after B is processed.

insert image description here

2. Java implements asynchronous callbacks

Implemented through the Future interface

The Future class exists in the concurrent package of the JDK, and its main purpose is to receive the results returned by Java's asynchronous thread computation.
java.util.concurrent.Future

  • Asynchronous execution
  • success callback
  • failure callback
public class FutureDemo {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 发送请求 Void:没有返回结果
        CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
    
    
            try {
    
    
                // 模仿耗时的操作
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"runAsync-》Void");
        });

        System.out.println(Thread.currentThread().getName()+"线程");
    }
}

Only the main thread executes
insert image description here

public class FutureDemo {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 发送请求 Void:没有返回结果
        CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
    
    
            try {
    
    
                // 模仿耗时的操作
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"runAsync-》Void");
        });

        System.out.println(Thread.currentThread().getName()+"线程");

        //获取阻塞执行结果
        Void result = future.get();
    }
}

insert image description here

// 有返回值的异步回调 supplyAsync
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
    
    
     System.out.println(Thread.currentThread().getName()+"runAsync-》Integer");
     //成功则返回1024
     return 1024;
 });

System.out.println(future.whenComplete((t, u) -> {
    
    
    System.out.println("t:" + t + "  u:" + u);
}).exceptionally((e) -> {
    
    
    System.out.println(e.getMessage());
    //失败则返回233
    return 233;
}).get());

insert image description here
Add a line of error code:

// 有返回值的异步回调 supplyAsync
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
    
    
    System.out.println(Thread.currentThread().getName()+"runAsync-》Integer");
    int i = 2/0;
    //成功则返回1024
    return 1024;
});

System.out.println(future.whenComplete((t, u) -> {
    
    
    System.out.println("t:" + t + "  u:" + u);
}).exceptionally((e) -> {
    
    
    System.out.println(e.getMessage());
    //失败则返回233
    return 233;
}).get());
}

insert image description here

2. JMM

JMM-----Java Memory Model

In java, all instance fields, static fields and array elements are stored in heap memory, which is shared among threads.
Local variables, method definition parameters, and exception handler parameters are not shared between threads, they do not have memory visibility issues, and they are not affected by the memory model.

JMM defines an abstract relationship between threads and main memory: shared variables between threads are stored in main memory, each thread has a private local memory, and local memory stores a copy of the thread's read/write shared variables. Local memory is an abstraction of the JMM that does not actually exist.

JMM's convention:

  1. Before the thread is unlocked, the shared variable must beimmediatelyFlush back to main memory.
    insert image description here
  2. Before a thread locks, it must read the latest value from main memory into working memory.
  3. Locking and unlocking are the same lock.

insert image description here
There are 8 kinds of memory interaction operations, and the virtual machine implementation must ensure that each operation isatom, indivisible (for double and long type variables, load, store, read and write operations allow exceptions on some platforms)

  • lock (lock): a variable that acts on main memory and identifies a variable as thread-exclusive.
  • unlock (unlock): a variable acting on the main memory, it releases a variable in a locked state, and the released variable can be locked by other threads.
  • read (read): acts on the main memory variable, it transfers the value of a variable from the main memory to the thread's working memory for use by the subsequent load action.
  • load (load): a variable that acts on working memory, it puts the read operation from the variable in main memory into working memory.
  • use (use): Acts on variables in working memory. It transmits variables in working memory to the execution engine. Whenever the virtual machine encounters a value that needs to be used, this instruction is used.
  • assign (assignment): acts on a variable in working memory, it puts a value received from the execution engine into a copy of the variable in working memory.
  • store (storage): acts on a variable in main memory, it transfers a value from a variable in working memory to main memory for subsequent write use.
  • write (write): acts on the variable in main memory, it puts the value of the variable obtained from the working memory by the store operation into the variable in the main memory.

JMM has formulated the following rules for the use of these eight instructions:

  • One of the read and load, store and write operations alone is not allowed. That is, if you use read, you must load, and if you use store, you must write.
  • The thread is not allowed to discard its most recent assign operation, that is, after the data of the work variable has changed, it must inform the main memory.
  • A thread is not allowed to synchronize unassigned data from working memory back to main memory.
  • A new variable must be created in main memory, and working memory is not allowed to use an uninitialized variable directly. That is, before implementing the use and store operations on the variable, it must go through the assign and load operations.
  • Only one thread can lock a variable at a time. After multiple locks, the same number of unlocks must be executed to unlock.
  • If the lock operation is performed on a variable, all the values ​​of the variable in the working memory will be cleared. Before the execution engine can use the variable, the value of the variable must be initialized by the reload or assign operation.
  • If a variable is not locked, it cannot be unlocked. Nor can you unlock a variable that is locked by another thread.
  • Before unlocking a variable, the variable must be synchronized back to main memory.

If thread A modifies the value of the shared variable, but the variable in thread B's local memory is still the old value, that is, thread B cannot see it in time. How to let B know that the value in main memory has changed? Use Volatile.

3*, Volatile

Volatile is a lightweight synchronization mechanism provided by the Java virtual machine.

  • Guaranteed visibility
  • Atomicity is not guaranteed
  • Disable instruction rearrangement

See in-depth understanding of the characteristics of volatile in java

4. Detailed CAS

What is CAS?
CAS is the abbreviation of Compare and Swap, which is to compare and replace.

There are 3 basic operands used in the CAS mechanism:memory address V, old expected value A, new value B to be modified.
When updating a variable, only when the expected value A of the variable and the actual value in the memory address V are the same, the value corresponding to the memory address V will be changed to B.

Assuming that there are multiple threads performing CAS operations and there are many CAS steps, is it possible that after judging that V and E are the same, when they are about to assign values, they switch threads and change the value. Caused data inconsistency?
The answer is no, because CAS is a system primitive. The primitive belongs to the category of operating system terms. It is composed of several instructions and is used to complete a process of a certain function, and the execution of the primitive must be continuous. It is not allowed to be interrupted during the execution process, that is to say, CAS is an atomic instruction of the CPU, which will not cause the so-called data inconsistency problem.

public class CASDemo {
    
    
    public static void main(String[] args) {
    
    
        AtomicInteger atomicInteger = new AtomicInteger(999);
        //compareAndSet(int expect, int update)
        System.out.println(atomicInteger.compareAndSet(999, 666));//true

        System.out.println(atomicInteger.get());//666
    }
}

ABA Questions:
If a variable V has the value A when it is first read, and when it is ready to be assigned, it is checked that it is still the value A, does that mean that its value has not been changed? No, because its value may be modified to a value B during this period of time, and then modified back to A, then CAS will mistakenly think that it has never changed.

In order to solve this problem, the JUC package provides a marked atomic reference class AtomicStampedReference, which can ensure the correctness of CAS by controlling the version of the variable value. However, this category is relatively useless.

Disadvantages of CAS:

  1. ABA problem.
  2. What the CAS mechanism guarantees is only the atomic operation of a variable, not the atomicity of the entire code block. For example, if you need to ensure that three variables are updated atomically together, you have to use Synchronized.
  3. The bottom layer of CAS is a spin lock. In the case of high concurrency, if many threads repeatedly try to update a variable, but the update is unsuccessful, the cycle repeats, which will put a lot of pressure on the CPU.

Guess you like

Origin blog.csdn.net/myjess/article/details/121392658