[JavaSE] JUC concurrent programming

foreword

This article is related to JUC concurrent programming knowledge, and the Java full-stack learning route can be referred to: [Java full stack learning route] The most complete Java learning route and knowledge list, Java self-study direction guide , which contains the most complete list of Java full-stack learning technologies~
Continued from this article: [JavaSE] JUC Concurrent Programming (Part 1)

Ten, blocking queue

1. Introduction to blocking queues

Collection frame:

Blocking queue features:

  • first in first out
  • When writing, if the queue is full, it must block and wait
  • When taking out, if the queue is empty, it must block waiting

2. Four sets of API s

WayThrow an exceptionHas a return value and does not throw an exceptionblocking, waitingblocking, waiting for timeout
Add toadd()offer()put()offer(,,)
removeremove()pull()take()pull(,)
Detect head elementelement()peek()--

Code example:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        test4();
    }
    // An exception was thrown: java.lang.IllegalStateException: Queue full
    public static void test1(){
        // The size of the queue is 3
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // add() method returns boolean value
        boolean flag1 = blockingQueue.add("a");
        boolean flag2 = blockingQueue.add("b");
        boolean flag3 = blockingQueue.add("c");
        boolean flag4 = blockingQueue.add("d");// add adding elements that exceed the length of the queue will throw an exception java.lang.IllegalStateException: Queue full
        System.out.println(blockingQueue.element());// Get the team leader element
        System.out.println("=========");
        // remove() returns the element removed this time
        Object e1 = blockingQueue.remove();
        Object e2 = blockingQueue.remove();
        Object e3 = blockingQueue.remove();
        Object e4 = blockingQueue.remove();// If there are no elements in the queue, continuing to remove elements will throw an exception java.util.NoSuchElementException
    }
    // Has a return value and does not throw an exception
    public static void test2(){
        // The size of the queue is 3
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // offer returns a boolean value
        boolean flag1 = blockingQueue.offer("a");
        boolean flag2 = blockingQueue.offer("b");
        boolean flag3 = blockingQueue.offer("c");
        //boolean flag4 = blockingQueue.offer("d");// offer to add elements that exceed the length of the queue will return false
        System.out.println(blockingQueue.peek());// Get the team leader element
        System.out.println("=========");
        // poll() returns the element removed this time
        Object poll1 = blockingQueue.poll();
        Object poll2 = blockingQueue.poll();
        Object poll3 = blockingQueue.poll();
        Object poll4 = blockingQueue.poll();// If there are no elements in the queue, continuing to remove elements will print null
    }
    // blocking, waiting
    public static void test3() throws InterruptedException {
        // The size of the queue is 3
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // put has no return value
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        //blockingQueue.put("d");// put adds elements that exceed the length of the queue and will keep waiting
        System.out.println("=========");
        // take() returns the element removed this time
        Object take1 = blockingQueue.take();
        Object take2 = blockingQueue.take();
        Object take3 = blockingQueue.take();
        Object take4 = blockingQueue.take();// If there are no elements in the queue and continue to remove elements, it will wait forever
    }
    // blocking, waiting for timeout
    public static void test4() throws InterruptedException {
        // The size of the queue is 3
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // offer returns a boolean value
        boolean flag1 = blockingQueue.offer("a");
        boolean flag2 = blockingQueue.offer("b");
        boolean flag3 = blockingQueue.offer("c");
        // The offer adds an element that exceeds the length of the queue and returns false; and waits for the specified time to be launched, and executes downward
        boolean flag4 = blockingQueue.offer("d", 2, TimeUnit.SECONDS);
        System.out.println("=========");
        // poll() returns the element removed this time
        Object poll1 = blockingQueue.poll();
        Object poll2 = blockingQueue.poll();
        Object poll3 = blockingQueue.poll();
        // If there are no elements in the queue and continue to remove elements, null will be printed, and then exit after waiting for the specified time.
        Object poll4 = blockingQueue.poll(2,TimeUnit.SECONDS);
    }
}

3.SynchronousQueue synchronization queue

  • Features: When you enter an element, you must wait for the element to be taken out before you can put down an element
  • Add element method: put()
  • Take out the element method: take()

Eleven, thread pool

1. Introduction to thread pools

  • The operation of the program, the essence: occupies the resources of the system! (optimized resource usage => pooling technology)
  • Pooling technology: prepare some resources in advance, if someone wants to use it, just come to me to get it, and return it to me after use
  • The benefits of thread pools: 1. Reduce the consumption of system resources; 2. Improve the speed of response; 3. Convenient management

One sentence summary for the thread pool: 3 major methods, 7 major parameters, 4 major rejection strategies

2. Three methods

  • Create a thread pool of a single thread: Executors.newSingleThreadExecutor();
  • Create a fixed size thread pool: Executors.newFixedThreadPool(5);
  • Create a scalable thread pool: Executors.newCachedThreadPool();

Code example:

package com.wang.pool;
import java.util.concurrent.ExecutorService;
import java.util.List;
import java.util.concurrent.Executors;
public class Demo01 {
    public static void main(String[] args) {
        // Executors tool class, 3 major methods
        // Executors.newSingleThreadExecutor();// Create a thread pool for a single thread
        // Executors.newFixedThreadPool(5);// Create a fixed size thread pool
        // Executors.newCachedThreadPool();// Create a scalable thread pool
        
        // Thread pool for a single thread
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        try {
            for (int i = 1; i < 100; i++) {
                // After using the thread pool, use the thread pool to create threads
                threadPool.execute(()->{
                    System.out.println(
                        Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // The thread pool is used up, the program ends, and the thread pool is closed
            threadPool.shutdown();
        }
    }
}

3. Seven parameters

int corePoolSize: core thread pool size
int maximumPoolSize: maximum core thread pool size
long keepAliveTime: the timeout will be released if no one calls
TimeUnit unit: Timeout unit
BlockingQueue workQueue: blocking queue
ThreadFactory threadFactory: thread factory: create threads, generally do not need to move
RejectedExecutionHandler handle: Rejection policy

Source code analysis:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService (
        new ThreadPoolExecutor(
            1, 
            1,
            0L, 
            TimeUnit.MILLISECONDS, 
            new LinkedBlockingQueue<Runnable>())); 
}
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
        5, 
        5, 
        0L, 
        TimeUnit.MILLISECONDS, 
        new LinkedBlockingQueue<Runnable>()); 
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(
        0, 
        Integer.MAX_VALUE, 
        60L, 
        TimeUnit.SECONDS, 
        new SynchronousQueue<Runnable>()); 
}
// Essentially ThreadPoolExecutor() 
public ThreadPoolExecutor(int corePoolSize, // Core thread pool size 
                          int maximumPoolSize, // Maximum core thread pool size 
                          long keepAliveTime, // The timeout will be released when no one calls 
                          TimeUnit unit, // timeout unit 
                          // blocking queue 
                          BlockingQueue<Runnable> workQueue, 
                          // Thread factory: Create threads, generally do not need to move
                          ThreadFactory threadFactory,  
                          // rejection policy
                          RejectedExecutionHandler handle ) {
    if (corePoolSize < 0 
        || maximumPoolSize <= 0 
        || maximumPoolSize < corePoolSize 
        || keepAliveTime < 0) 
        throw new IllegalArgumentException(); 
    if (workQueue == null 
        || threadFactory == null 
        || handler == null) 
        throw new NullPointerException(); 
    this.acc = System.getSecurityManager() == null 
        ? null : AccessController.getContext(); 
    this.corePoolSize = corePoolSize; 
    this.maximumPoolSize = maximumPoolSize; 
    this.workQueue = workQueue; 
    this.keepAliveTime = unit.toNanos(keepAliveTime); 
    this.threadFactory = threadFactory; 
    this.handler = handler; 
}

Because the tool class Executors is not safe in actual development, it is necessary to manually create a thread pool and customize 7 parameters.
Sample code:

package com.wang.pool;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
// Executors tool class, 3 major methods
// Executors.newSingleThreadExecutor();// Create a single thread thread pool
// Executors.newFixedThreadPool(5);// Create a fixed size thread pool
// Executors.newCachedThreadPool();// Create a scalable thread pool
/**
 * Four rejection strategies:
 *
 * new ThreadPoolExecutor.AbortPolicy() 
 * The bank is full and someone comes in. If this person is not processed, an exception will be thrown
 *
 * new ThreadPoolExecutor.CallerRunsPolicy() 
 * Where did it come from? For example, your father asks you to tell your mother to do laundry, but your mother refuses and asks you to go back and tell your father to do the laundry.
 *
 * new ThreadPoolExecutor.DiscardPolicy() 
 * When the queue is full, the task is dropped, no exception will be thrown!
 *
 * new ThreadPoolExecutor.DiscardOldestPolicy() 
 * When the queue is full, try to compete with the earliest one without throwing an exception!
 */
public class Demo01 {
    public static void main(String[] args) {
        // Custom thread pool! Job ThreadPoolExecutor
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,// int corePoolSize, the size of the core thread pool (2 windows in the waiting area)
                5,// int maximumPoolSize, the maximum core thread pool size (5 windows in total) 
                3,// long keepAliveTime, the timeout will be released if no one calls for 3 seconds, and the window will be closed 
                TimeUnit.SECONDS,// TimeUnit unit, timeout unit seconds 
                new LinkedBlockingDeque<>(3),// Blocking queue (up to 3 people in the waiting area)
                Executors.defaultThreadFactory(),// Default thread factory
                // One of 4 rejection strategies:
                // When the queue is full, try to compete with the earliest one, and no exception will be thrown!
                new ThreadPoolExecutor.DiscardOldestPolicy());  
        //When the queue is full, try to compete with the earliest one without throwing an exception!
        try {
            // Maximum load: Deque + max
            // Exceeds RejectedExecutionException
            for (int i = 1; i <= 9; i++) {
                // After using the thread pool, use the thread pool to create threads
                threadPool.execute(()->{
                    System.out.println(
                        Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // The thread pool is used up, the program ends, and the thread pool is closed
            threadPool.shutdown();
        }
    }
}

4. Four major rejection strategies

  • new ThreadPoolExecutor.AbortPolicy() : The thread queue is full, the thread is not processed, and an exception is thrown!
  • new ThreadPoolExecutor.CallerRunsPolicy() : Give it to the main thread to process!
  • new ThreadPoolExecutor.DiscardPolicy() : The queue is full, the task will be discarded, and no exception will be thrown!
  • new ThreadPoolExecutor.DiscardOldestPolicy() : The queue is full, try to compete with the earliest one, and no exception will be thrown!

5. How should the maximum threads be set?

  • CPU-intensive: the maximum number of threads, the number of CPU cores is just a few, which can maintain the highest CPU efficiency.
  • IO-intensive: determine the number of threads in the program that consume a lot of IO. If it is greater than this number, it is generally twice the number.

12. Four functional interfaces

Functional interface: An interface with only one method.
Four functional interfaces:

  • Function functional interface: has one input parameter and one output (return value).
  • predicate assertion interface: there is one input parameter, and the return value can only be a boolean value.
  • Consumer consumer interface: has one input parameter and no return value.
  • supplier interface: no input parameters, there is an output (return value).

Thirteen, stream stream computing

  • Stream stream computing is a new feature introduced after jdk 1.8, which converts a collection or array into a sequence of elements of a stream. A stream is not an element in a collection, nor is it a data structure, and is not responsible for the storage of data. Stream streams also do not mutate the source object (source collection).
  • The parameters of almost all methods in the Stream interface are parameters of the four major functional interface types. The functional interface can use lambda expressions to simplify development, and the methods in the Stream interface basically return the object itself (the method that returns the object itself can use chain programming). Therefore, when using Stream stream computing, functional interfaces, lambda expressions and chain programming are basically used.
  • Collection interface added a stream() method after jdk 1.8, calling this method will get a Stream object. Through this object, the corresponding methods of the Stream interface can be called to filter, sort, truncate (discard), truncate (acquire), convert, traverse, count, concatenate, take the maximum value, and take the minimum value.

Code example:

/**
 * Filter users, five conditions
 * 1.ID Is an even number 2. The age is greater than 23 years old 3. The username is converted to uppercase letters 4. The username letters are sorted backwards 5. Only one user is output
 */
public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,"A",23);
        User u2 = new User(2,"B",21);
        User u3 = new User(3,"C",25);
        User u4 = new User(4,"D",30);
        User u4 = new User(5,"E",28);
        //collection is storage
        List<User> list = Arrays.asList(u1,u2,u3,u4);
        //The calculation is handed over to the Stream
        list.stream()
                .filter(user -> {return user.getId()%2==0;})
                .filter(user -> {return user.getAge()>23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

14. Detailed explanation of Forkjoin

1. What is ForkJoin

  • ForkJoin is also a thread pool, but ForkJoin is a thread pool specially established for CPU-intensive tasks, which can greatly improve the execution efficiency of CPU-intensive tasks. Mainly used to execute tasks in parallel, improve efficiency, and large data volume.
  • ForkJoin is implemented using a divide-and-conquer algorithm. The main principle is to divide a large task into several small tasks and distribute them to several threads for processing. Finally, the processed results of several threads are aggregated to achieve improved computing. Efficiency results.

2.forkjoinPool

  • (1) Execute ForkJoin through forkjoinPool
  • (2) Computing task: forkjoinPool.execute(ForkJoinTask task)

Code example:

public class ForkJoinDemo extends RecursiveTask<long> {
    private Long start;//1
    private Long end;//19990009
    //critical value
    private Long temp = 10000L;
    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }
    //calculation method
    @Override
    protected long compute() {
        if ((end-start)<temp){
            int sum = 0;
            for (int i=1;i<10_0000_0000;i++){
                sum+=i;
            }
            return sum;
        }else {
            //take an intermediate value
            long middle = (start + end) / 2;
            ForkJoinDemo forkJoinDemo1 = new ForkJoinDemo(start,middle);
            //Split the task and push the task to the thread queue
            forkJoinDemo1.fork();
            ForkJoinDemo forkJoinDemo2 = new ForkJoinDemo(middle+1,end);
            //Split the task and push the task to the thread queue
            forkJoinDemo2.fork();
            return forkJoinDemo1.join()+forkJoinDemo2.join();
        }
    }
}

3. Three ways to perform computing tasks

public static void test1(){
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (int i=1;i<10_0000_0000;i++){
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+"time:"+(end-start));
    }
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L,10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+"time:"+(end-start));
    }
    public static void test3(){
        long start = System.currentTimeMillis();
        //stream parallel stream
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+"time:"+(end-start));
    }

Result: test3 is faster than test2 is faster than test1.

15. Asynchronous callback

1.Future

  • The original intention of future design is to model the outcome of an event in the future
  • The essence is that the front end sends an Ajax asynchronous request to the back end
  • CompletableFuture is used more in the implementation class

2. runAsync asynchronous callback without return value

public static void main(String[] args) throws ExecutionException, InterruptedException {
        //make a request
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"implement");
        });
        System.out.println("1111");
        //Get blocking execution result
        completableFuture.get();
    }

3. The supplyAsync asynchronous callback with return value

  • The whenComplete() method has two parameters, T represents the normal return result, and U represents the error message that throws an exception
  • If an exception occurs, the get() method will get the information in exceptionally
public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"implement");
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t,u)->{
            System.out.println("t:"+t+";u="+u);
        }).exceptionally((e)->{
            System.out.println(e.getMessage());
            return 233;
        }).get());
    }

16. JMM

1.Volatile

  • Volatile is actually a lightweight synchronization mechanism provided by the java virtual machine
  • Volatile has three characteristics: guarantees visibility, does not guarantee atomicity, and prohibits instruction reordering

Guaranteed visibility

//Without volatile program, there will be an infinite loop, which can ensure visibility
  private volatile static int num = 0;
  public static void main(String[] args) {
      new Thread(()->{//Thread 1 is unaware of memory changes
          while (num == 0){
          }
      }).start();
      try {
          TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      num = 1;
      System.out.println(num);
  }

Atomicity is not guaranteed
Atomicity: that is, indivisible. When thread A performs tasks, it cannot be disturbed and cannot be divided, either succeeding or failing at the same time

public class tvolatile {
    //Verification that does not guarantee atomicity
    private volatile static int num = 0;
    public void add(){
        num++;
    }
    public static void main(String[] args) {
        //Theoretically the result should be 20000, but in fact it is less than 20000
        for (int i=0;i<=20;i++){
            new Thread(()->{
                for (int j=0;j<1000;j++){
                    add();
                }
            }).start();
        }
    }
}

Disable instruction rearrangement

  • What is instruction reordering? The program is not executed as we wrote it, the compiler will optimize the rearrangement first, instruction parallelism may also be rearranged, and the memory system will also be rearranged. When the processor reorders instructions, it will consider the dependencies between data
  • Instruction rearrangement may have an impact. For multi-threaded situations, there may be an impact. The solution is Volatile.
  • Volatile can avoid instruction rearrangement: (1) Memory barrier: It is a CPU instruction that can guarantee a specific order of operations, and can guarantee the memory visibility of certain variables. Using these features, Volatile can achieve visibility; (2) Volatile can guarantee visibility, but cannot guarantee atomicity. Due to the memory barrier, it can guarantee to avoid the phenomenon of instruction rearrangement.

2. What is JMM

JMM is a requirement, a concept, that is, the java memory model.
There are some synchronization conventions about JMM:

  • Before the thread is unlocked, the shared variable must be flushed back to main memory immediately
  • A thread locks money and must read the latest value in main memory into working memory
  • Locking and unlocking are the same lock

3. 8 operations of JMM

  • Read (read): acts on the main memory variable, which 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 acting on working memory, which puts the read operation from the variable in the main memory into the working memory;
  • 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 use a variable, it will use this instruction;
  • assign (assignment): acting 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): acting on variables 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 the main memory, it puts the value of the variable obtained by the store operation from the working memory into the variable in the main memory;
  • lock (lock): a variable acting on the main memory, marking a variable as a thread-exclusive state;
  • 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;

4. Relevant regulations of the 8 operations of JMM

  • 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 his most recent assign operation, that is, after the data of the working variable has changed, the main memory must be informed
  • A thread is not allowed to synchronize un assign ed 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 use and store operations on variables, assign and load operations must be performed.
  • Only one thread can lock a variable at a time. After multiple locks, the same number of unlocks must be executed to unlock
  • If a lock operation is performed on a variable, the value of this variable in all working memory will be cleared. Before the execution engine uses this variable, the value of the variable must be initialized by re load or assign operation.
  • If a variable is not locked, it cannot be unlock ed. Also cannot unlock a variable that is locked by another thread
  • Before unlock ing a variable, the variable must be synchronized back to main memory

Seventeen, singleton mode

The Singleton Pattern is one of the simplest design patterns in Java. This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its only object, directly, without instantiating an object of the class.

1. Hungry-style singleton pattern

/**
 * Hungry-style singleton mode, create a singleton object as soon as it is loaded
 */
public class Hungry {
    private Hungry(){
    }
    private static final Hungry hungry = new Hungry();
    public static Hungry getInstance(){
        return hungry;
    }
}

2. Lazy singleton pattern

/**
 * Lazy singleton pattern
 */
public class LazyMan {
    private LazyMan() {
        System.out.println(Thread.currentThread().getName());
    }
    private static LazyMan lazyMan;
    public static LazyMan getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
    // There is a problem with multithreading concurrently creating objects.
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}
// Print
// Thread-1
// Thread-0
// Thread-4
// Thread-2

3. Double detection lock singleton mode DCL

  • Relatively safe pattern to create singletons, but reflection can break
/**
 * Lazy singleton pattern
 */
public class LazyMan01 {
    private LazyMan01(){
        System.out.println(Thread.currentThread().getName());
    }
    // Add the volatile keyword to prohibit instruction rearrangement
    private volatile static LazyMan01 lazyMan01;
    /**
     * Double detection lock mode, DCL lazy style
     * @return
     */
    public static LazyMan01 getInstance(){
        if (lazyMan01==null){
            synchronized(LazyMan01.class){
                if (lazyMan01==null){
                    lazyMan01 =  new LazyMan01();// Not an atomic operation, there may be instruction reordering
                    /*
                    * 1.Allocate memory space
                    * 2.Execute the constructor, initialize the object
                    * 3.Point the object to the memory space
                    * Under normal circumstances: execute according to 123; if instruction rearrangement occurs, 132 may be executed first
                    * In the case of multi-threading: if the first thread executes 13, the second thread may judge that lazyMan01 is not empty and return lazyMan01 directly.
                    * At this point, the lazyMan01 object memory is empty.
                    * */
                }
            }
        }
        return lazyMan01;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan01.getInstance();
            }).start();
        }
    }
}

4. Static inner class to create singleton pattern

/**
 * Static inner class to create singleton pattern
 */
public class LazyMan02 {
    private LazyMan02(){
    }
    public static LazyMan02 getInstance(){
        return InnerClass.lazyMan02;
    }
    static class InnerClass{
        private static final LazyMan02 lazyMan02 = new LazyMan02();
    }
}

5. Enumerate to create a singleton

public enum EnumSingle {
    INSTANCE;
    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}

18. Detailed explanation of CAS

1. What is CAS

If volatile is not added when constructing lazy, then this is not an atomic operation: it will allocate memory space first, then execute the constructor, initialize the object, and then point the object to this space. At this time, the lazy may not complete the operation.
Code example:

public class Lazy {
    private Lazy(){
    }
    private volatile static Lazy lazy;
    //The lazy singleton of the double detection lock pattern: the DCL lazy
    public static Lazy getInstance(){
        //lock
        if (lazy==null){
            synchronized (Lazy.class){
                if (lazy==null){
                    lazy = new Lazy();//If volatile is not added when constructing lazy, then this is not an atomic operation: first allocate memory space, then execute the constructor, initialize the object, and then point the object to this space
                }
            }
        }
        return lazy;//At this point lazy may not complete the operation
    }
    //Multi-threaded concurrency
    public static void main(String[] args) {
        for (int i=0;i<10;i++){
            new Thread(()->{
                Lazy.getInstance();
            }).start();
        }
    }
}

2.compareAndSet() method

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //There are two parameters in the compareAndSet() method, except: expected value, update: updated value
        //If the value I expect is reached, update it, otherwise don't update it
        System.out.println(atomicInteger.compareAndSet(2020,2021));
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement();//Equivalent to number++
        System.out.println(atomicInteger.compareAndSet(2020,2021));
        System.out.println(atomicInteger.get());
    }
}

3. Advantages and disadvantages of CAS

advantage:

  • Compare the value in the current working memory with the value in the main memory, if the value is expected, then execute, if not, keep looping

shortcoming:

  • The cycle will take time
  • Only one shared variable is guaranteed to be atomic at a time
  • There will be ABA problems

4. What is an ABA problem

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //messy thread
        System.out.println(atomicInteger.compareAndSet(2020,2021));
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2021,2020));
        System.out.println(atomicInteger.get());
        //desired thread
        System.out.println(atomicInteger.compareAndSet(2020,2021));
        System.out.println(atomicInteger.get());
    }
}
// Results of ABA Questions
// Print
// true
// 2021
// true
// 2020
// true
// 2021

5. How to Solve the ABA Problem: Atomic References

  • To solve the ABA problem, atomic references must be introduced, and the corresponding idea is: optimistic locking
public class CASDemo02 {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1, 1);
        new Thread(()->{
            int stamp = atomicInteger.getStamp();//get version number
            System.out.println("a1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicInteger.compareAndSet(1, 2,atomicInteger.getStamp(),atomicInteger.getStamp()+1));
            System.out.println("a2=>"+atomicInteger.getStamp());
            System.out.println(atomicInteger.compareAndSet(2, 1,atomicInteger.getStamp(),atomicInteger.getStamp()+1));
            System.out.println("a3=>"+atomicInteger.getStamp());
        },"a").start();
        new Thread(()->{
            int stamp = atomicInteger.getStamp();//get version number
            System.out.println("b1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicInteger.compareAndSet(1,4,stamp,stamp+1);
            System.out.println("b2=>"+atomicInteger.getStamp());
        },"b").start();
    }
}

postscript

Java full stack learning route can refer to: [Java full stack learning route] The most complete Java learning route and knowledge list, Java self-study direction guide , which contains the most complete list of Java full-stack learning technologies~

Tags: Java

Posted by Incessant-Logic on Fri, 14 Oct 2022 23:47:50 +1030