juc notes (previous)

JUC

1. Review of prepositional concepts

start thread

public static void main(String[] args) throws InterruptedException
{
    Thread t1 = new Thread(() -> {
    }, "t1");
    t1. start();
}

The method in t1.start() is

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

And the start0() method

private native void start0();

native calls the third-party c language interface. The Java bottom layer is implemented in c + +.

So native start0() calls thread C, jvm.cpp, thread.cpp method

The specific paths of the three files are:

  • The java thread is started and executed through the start method. The main content is in the native method start0,
  • openjdk write JNI is generally one-to-one correspondence, thread Java corresponds to Thread.c
  • start0 is actually the JVM_StartThread. At this time, you can see the declaration in jvm.h and the implementation in jvm.cpp by looking at the source code.

That is, finally

os::start_ thread(thread);

This line of code calls the system to allocate a native base thread.

Related concepts of java multithreading

1. A lock

synchronized

2. Two combinations

1) concurrent
  • Are multiple events on the same entity
  • Processing multiple tasks simultaneously on one processor
  • At the same time, there is only one event happening
2) parallel
  • Are in different entities_ Multiple events on
  • It is to process multiple tasks simultaneously on multiple processors
  • At the same time, everyone is really doing things, you do what you do, I do what I do, but we are all doing it
3) Concurrent vs Parallel

3. Three processes

1) Progress

In short, an application running in the system is a process, and each process has its own memory space and system resources.

2) Thread

Also known as a lightweight process, there will be one or more threads in the same process. It is the basic unit for timing scheduling in most operating systems.

3) Tube

Monitor, which is what we usually call a lock

Monitor is actually a synchronization mechanism. Its obligation is to ensure that (at the same time) only one thread can access the protected data and code.

Synchronization in the JVM is realized based on entering and exiting the monitor object (monitor, management object). Each object instance will have -. Monitor objects,

Object o = new Object();
new Thread(()->{
    synchronized (o){

    }
},"t2").start();

The Monitor object will be created and destroyed together with the Java object. Its bottom layer is implemented by the C + + language.

JVM version 3

User thread and daemon thread

Java threads are divided into user threads and daemon threads

  • In general, the configuration is not specified, and the default is the user thread.
  • User thread

It is the working thread of the system, which will complete the business operations required by this program. For example: main thread

  • Daemon thread

    It is a special thread that serves other threads and completes it silently in the background - some systematic services, such as garbage collection threads, are the most typical examples

    As a service thread, there is no need to continue running without a service object. If all the user threads have ended, it means that the business operations that the program needs to complete have ended, and the system can exit. So if there are only daemon threads left in the system, the java virtual machine will exit automatically.

daemon property of thread

  • true indicates that it is a daemon thread
  • false indicates that it is a user thread

code demo

Not open

public class DaemonDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t Start execution,"+(Thread.currentThread().isDaemon()?"Daemon thread":"User thread"));
            while(true){

            }
        },"t1");
      //  thread.setDaemon(true);
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "\t ---end Main thread");
    }
}

Execution result:

Start the daemon thread (setDaemon should be before the start method, otherwise an error will be reported)

public class DaemonDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println(Thread.urrentThread().getName()+"\t Start execution,"+(Thread.currentThread().isDaemon()?"Daemon thread":"User thread"));
            while(true){

            }
        },"t1");
        thread.setDaemon(true);
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "\t ---end Main thread");
    }
}

Summary:

If all the user threads end, it means that the business operations that the program needs to complete have ended. The daemon thread ends working with the JVM. The setDaemon(true) method must be set before start(), otherwise, an exception of llelgalIThreadStateException will be reported

2.CompletableFuture

Review of Future interface theory

  • The Future interface (FutureTask implementation class) defines some methods for operating asynchronous task execution, such as obtaining the execution result of asynchronous task, canceling task execution, judging whether the task is canceled, judging whether the task execution is completed, etc.
  • For example, the main thread allows a sub thread to execute a task. The sub thread may be time-consuming. After starting the sub thread to execute a task, the main thread will do other things, busy with other things or finish executing first. After a while, it will get the execution result of the sub task or the changed task status.

In a word, the Future interface can open a branch task for the main thread, which is specially used for processing time-consuming and laborious complex business for the main thread.

Common implementation class of Future interface: FutureTask asynchronous task

1. What can future interface do:

  • Future is a new interface added in Java 5, which provides a function of asynchronous parallel computing.

    Runnable interface

  • If the main thread needs to execute a very time-consuming computing task, we can put this task into an asynchronous thread through future.

  • The main thread continues to process other tasks or ends first, and then obtains the calculation results through Future.

  • Code speak:

    Callable interface

    Future interface and

    Future Task implementation class

  • Purpose: asynchronous multithreaded tasks execute and return results. Three characteristics: multithreaded / return / asynchronous tasks

  • (the monitor goes to buy water for the teacher as a newly started asynchronous multithreading task, and the result of buying water is returned)

Case:

import java.util.concurrent.Callable;

public class CompletableFutureDemo {
    public static void main(String[] args) {

    }
}

/**
 * no return value
 */
class MyThread implements Runnable {
    @Override
    public void run() {

    }
}

/**
 * With return value
 */
class MyThread2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        return null;
    }
}

2. Related architecture of the Future interface of the source

Case:

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new MyThread1());

        Thread t1 = new Thread(futureTask,"t1");
        t1.start();
        System.out.println(futureTask.get());
    }
}
class MyThread1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("---come in call()");
        return "hello Callable";
    }
}

3.Future coding practice and advantages and disadvantages analysis

advantage
  • future + thread pool and asynchronous multi-threaded task cooperation can significantly improve the execution efficiency of the program.
  • Code case:
import java.util.concurrent.*;

public class FutureThreadPollDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //Set 3 thread pools
        ExecutorService threadPool= Executors.newFixedThreadPool(3);
        long l = System.currentTimeMillis();
        FutureTask<String > futureTask1=new FutureTask<>(()->{
            try{TimeUnit.MILLISECONDS.sleep(500);}catch (InterruptedException e){e.printStackTrace();}
            return "task1 over";
        });
        threadPool.submit(futureTask1);
        FutureTask<String > futureTask2=new FutureTask<>(()->{
            try{TimeUnit.MILLISECONDS.sleep(300);}catch (InterruptedException e){e.printStackTrace();}
            return "task2 over";
        });
        threadPool.submit(futureTask2);
        FutureTask<String > futureTask3=new FutureTask<>(()->{
            try{TimeUnit.MILLISECONDS.sleep(300);}catch (InterruptedException e){e.printStackTrace();}
            return "task3 over";
        });
        threadPool.submit(futureTask3);

        System.out.println(futureTask1.get());
        System.out.println(futureTask2.get());
        long l1 = System.currentTimeMillis();
        System.out.println("--------costTime:"+(l1-l)+"millisecond");
        System.out.println(Thread.currentThread().getName()+"\t---end");
        threadPool.shutdown();

        System.out.println("------------");
        m1();
    }

    private static void m1() {
        long l = System.currentTimeMillis();
        try{TimeUnit.MILLISECONDS.sleep(500);}catch (InterruptedException e){e.printStackTrace();}
        try{TimeUnit.MILLISECONDS.sleep(300);}catch (InterruptedException e){e.printStackTrace();}
        try{TimeUnit.MILLISECONDS.sleep(300);}catch (InterruptedException e){e.printStackTrace();}
        long l1 = System.currentTimeMillis();
        System.out.println("--------costTime:"+(l1-l)+"millisecond");
        System.out.println(Thread.currentThread().getName()+"\t---end");
    }
}

Execution result:

shortcoming
get() blocking
  • get() will not leave until the result, whether the calculation is completed or not. It is easy to cause congestion. Generally, the get method is placed at the end
FutureTask<String> futureTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + "\t---------come in");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "task over";
        });
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName() + "\t------------Busy with other tasks");

Operation results

A little solution:

Add parameters after get

System.out.println(futureTask.get(3,TimeUnit.SECONDS));

Operation results:

isDone() polling

Polling will consume unnecessary CPU resources, and may not get the results in time
If you want to get the results asynchronously, you will usually get the results by polling. Try not to block

FutureTask<String> futureTask = new FutureTask<>(() -> {
    System.out.println(Thread.currentThread().getName() + "\t---------come in");
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "task over";
});
Thread t1 = new Thread(futureTask, "t1");
t1.start();

System.out.println(Thread.currentThread().getName() + "\t------------Busy with other tasks");
// System.out.println(futureTask.get(3,TimeUnit.SECONDS));
while (true) {
    if (futureTask.isDone()) {
        System.out.println(futureTask.get());
        break;
    } else {
        //Pause (MS)
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("It's being processed. Don't push it any more. The more you push it, the slower it will be. Then push it out");
    }
}

result:

conclusion

Future is not very friendly to obtain results. It can only get the results of tasks by blocking or polling.

The improvement of completable Future on Future

Why does completable future appear

  • The get() method will always be in the blocking state until the Future calculation is completed,
  • The isDone() method easily consumes CPU resources,
  • For real asynchronous processing, we hope that the callback function can be automatically called at the end of the Future by passing in the callback function, so that we do not need to wait for the result.
  • Blocking is contrary to the design concept of asynchronous programming, and polling will consume unnecessary CPU resources. Therefore,
  • JDK8 designs a completable future.
  • Completable future provides a mechanism similar to the observer mode, which allows the listener to be notified after the task is completed.

The source codes of completable future and CompletionStage are introduced respectively

Class schema description

Interface CompletionStage
  • CompletionStage represents a certain stage in the asynchronous calculation process. After one stage is completed, another stage may be triggered
  • The calculation execution of a stage can be a Function, Consumer or Runnable. For example: stage thenApply(x -> square()). thenAccept(x -> System.outprint()). thenRun() -> System.out.println()
  • The execution of a stage may be triggered by the completion of a single stage, or it may be triggered by multiple stages together

It represents a certain stage in the asynchronous calculation process. After one stage is completed, another stage may be triggered. Some are similar to the pipeline separator of the Linux system.

Class completable future
  • In Java 8, completable Future provides a very powerful extension function of Future, which can help us simplify the complexity of asynchronous programming, provide the ability of functional programming, bury the calculation results through callback, and provide the method of converting and combining completable Future.
  • It may represent an explicitly completed Future, or it may represent a completion stage. It supports triggering some functions or executing some actions after the calculation is completed
  • It implements the Future and CompletionStaqe interfaces

Core four static methods to create an asynchronous task

runAsync has no return value
  • public static CompletableFuture runAsync(Runnable runnable)
  • public static CompletableFuture runAsync(Runnable runnable ,Executor executor)
supplyAsync has a return value
  • public static CompletableFuture supplyAsync(Supplier supplier)
  • public static CompletableFuture supplyAsync(Supplier supplier,Executor executor)
Description of the above Executor executor parameters
  • The Executor method is not specified, and the default ForkJoinPool.commonPool() is directly used as its thread pool to execute asynchronous code.
  • If a thread pool is specified, the asynchronous code is executed using our custom or specially specified thread pool,
Code

No return value

With return value

General demonstration of Code to reduce blocking and polling

Completable Future has been introduced since Java 8. It is an enhanced version of Future to reduce blocking and polling
You can pass in a callback object, and automatically call the callback method of the callback object when the asynchronous task is completed or an exception occurs

CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "------com in");
    int result = ThreadLocalRandom.current().nextInt(10);
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("-------1 The result will be displayed after seconds:" + result);
    return result;
}).whenComplete((v,e)->{
     if (e==null) {
         System.out.println("----Calculation completed,Update system updateVa: "+v);
     }
 }).exceptionally(e->{
     e.printStackTrace();
     System.out.println("Abnormal conditions:"+e.getCause()+"\t"+e.getMessage());
     return null;
 });
System.out.println(Thread.currentThread().getName()+"The thread is busy with other things first");

Under this method, the default thread pool is closed. Remember to close the custom thread pool

try {
    TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
    e.printStackTrace();
}

Or join Executor

ExecutorService threadPool = Executors.newFixedThreadPool(3);
Advantages of completable future
  • When the asynchronous task ends, it will automatically call back the method of an object;
  • After the main thread has set the callback, it no longer cares about the execution of asynchronous tasks. Asynchronous tasks can be executed sequentially
  • When an asynchronous task makes an error, it will automatically call back the method of an object;

Case study - from the price comparison demand of e-commerce websites

Functional interface example

Functional interface nameMethod name:parameterReturn value:
RunnablerunNo parametersNo return value
Functionapply1 parameterNo return value
consumeaccept1 parameterNo return value
sippiergetNo parametersWith return value
BiConsumeraccept2 ParametersNo return value

Difference between join and get

join does not check for exceptions at runtime. get to

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            return "hello 1234";
        });
//        System.out.println(completableFuture.get());
        System.out.println(completableFuture.join());

Operation results:

The demand of large factories is "function first" – > perfect

requirement analysis

(1) requirements description

1.1 search the price of the same product on all major e-commerce platforms at the same time;
1.2 for the same product, search out the price of each seller in the same e-commerce platform

2 output return:

The result is the price List of the same product in different places, and a List is returned
<mysq|l>in jd price is 88.05
<mysq|>in dangdang price is 86.11
<mysq|>in taobao price is 90.43

3 solution: compare the prices of the same commodity on various platforms, and obtain a list,
  • Step by step, step by step, after checking JD, Taobao, Taobao and tmall
  • all in, ten thousand arrows are fired, one by one multi-threaded asynchronous tasks are queried at the same time.....
public class CompletableFutureMallDemo {
    static  List<NetMail> list= Arrays.asList(
        new NetMail("jd"),
        new NetMail("dangdang"),
        new NetMail("taobao"),
        new NetMail("pdd"),
        new NetMail("tmall")
    );
    /**
     * Search from house to house
     *
     * */
    public static List<String> getPrice( List<NetMail> list, String productName){
        return list
            .stream()
            .map(netMail -> String.format(productName + " in %s price is %.2f", netMail.getNetMallName(),
                                          netMail.calcPrice(productName)))
            .collect(Collectors.toList());
    }

    /**
     *Concurrent search
     * */
    public static  List<String> getPriceByCompletableFuture(List<NetMail> list,String productName){
        return list.stream().map(netMail ->
                                 CompletableFuture.supplyAsync(() -> String.format(productName + "in %s price is %.2f",
                                                                                   netMail.getNetMallName(),
                                                                                   netMail.calcPrice(productName))))
            .collect(Collectors.toList())
            .stream().map(CompletableFuture::join)
            .collect(Collectors.toList());
    }
    public static void main(String[] args)  {
        long startTime = System.currentTimeMillis();
        List<String> list1 = getPrice(list, "mysql");
        for (String element: list1){
            System.out.println(element);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("----costTime:"+(endTime-startTime)+"millisecond");
        System.out.println("------------");
        long startTime2 = System.currentTimeMillis();
        List<String> list2 = getPriceByCompletableFuture(list, "mysql");
        for (String element: list2){
            System.out.println(element);
        }
        long endTime2 = System.currentTimeMillis();
        System.out.println("----costTime:"+(endTime2-startTime2)+"millisecond");
    }
}

class NetMail{
    @Getter
    private String netMallName;

    public NetMail(String netMallName) {
        this.netMallName = netMallName;
    }

    public double calcPrice(String productName){
        try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e){e.printStackTrace();}
        return   ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
    }
}

Operation results

mysql in jd price is 110.71
mysql in dangdang price is 110.30
mysql in taobao price is 110.73
mysql in pdd price is 109.10
mysql in tmall price is 109.91
----costTime:5100 millisecond
------------
mysqlin jd price is 110.15
mysqlin dangdang price is 110.19
mysqlin taobao price is 109.42
mysqlin pdd price is 109.37
mysqlin tmall price is 109.92
----costTime:1017 millisecond

Common methods of completable future

1. Obtain results and trigger calculation

Get results

  • public T get()

    Meet or leave

  • public T get(long timeout, TimeUnit unit)

    Out of date

  • public T join()

    Similar to the get method, but does not check for exceptions before running

  • public T getNow(T valuelfAbsent)

    If the calculation is not completed, give me an alternative result

    Get results immediately without blocking

    After calculation, return the result after calculation

    Not finished, return the set valuelfAbsent value

//            throws ExecutionException, InterruptedException, TimeoutException
{
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "abc";
    });
    //        System.out.println(completableFuture.get());
    //        System.out.println(completableFuture.get(2,TimeUnit.SECONDS));
    //        System.out.println(completableFuture.join());
    try{TimeUnit.SECONDS.sleep(3);}catch(InterruptedException e){e.printStackTrace();}
    System.out.println(completableFuture.getNow("xxx"));
}

Active trigger calculation

  • public boolean complete(T value )
System.out.println(completableFuture.complete("completeValue")+"\t"+completableFuture.join());

2. Process the calculation results

thenApply

There is a dependency between the calculation results, and the two threads are serialized

Abnormal correlation

  • Due to the dependency relationship (the current step is wrong and the next step is not taken), the current step will be stopped if it is abnormal.
handle

There is a dependency between the calculation results, and the two threads are serialized

Abnormal correlation

  • If there is an exception, you can go next step. You can handle it step by step according to the exception parameters

3. Consume the calculation results

Receive the processing result of the task and consume it. No result is returned
thenAccept
CompletableFuture.supplyAsync(()-> 1).thenApply(f-> f+2).thenApply(f-> f+3).thenAccept(System.out::println);
Comparison and supplement
Execution order between code s

thenRun

  • thenRun(Runnable runnable)

  • Task A has finished executing task B, and B does not need the result of A

thenAccept

  • thenAccept(Consumer action)
  • Task A has finished executing task B, and B needs the result of task A, but Task B has no return value

thenApply

  • thenApply(Function fn)
  • After task A executes Task B, B needs the result of task A, and Task B has A return value
Completable future and thread pool description
ExecutorService service = Executors.newFixedThreadPool(5);
try {
    CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1 Task No" + "\t" + Thread.currentThread().getName());
        return "abcd";
    },service).thenRunAsync(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("2 Task No" + "\t" + Thread.currentThread().getName());
    }).thenRunAsync(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("3 Task No" + "\t" + Thread.currentThread().getName());
    }).thenRunAsync(() -> {
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("4 Task No" + "\t" + Thread.currentThread().getName());
    });
    System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
} catch (ExecutionException | InterruptedException | TimeoutException e) {
    e.printStackTrace();
} finally {
    service.shutdown();
}
1. No user-defined thread pool is passed in, and the default thread pool ForkJoinPool is used;
2 passed in a custom thread pool,

If you pass in a custom thread when executing the first task:

  • When calling the thenRun method to execute the second task, the second task and the first task share the same thread pool.
  • When calling thenRunAsync to execute the second task, the first task uses the thread pool you passed in, and the second task uses the ForkJoin thread pool
(3) remarks:

The processing may be too fast. The system optimizes the switching principle and directly uses the main thread for processing
Other examples include thenAccept and thenAcceptAsync, thenApply and thenApplyAsync, and the difference between them is the same

What is the difference between thenRun and thenRunAsync?

4. Select the calculation speed

Who can use it
apply ToEither
CompletableFuture<String> playA = CompletableFuture.supplyAsync(() -> {
    System.out.println("A come in");
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "playA";
});

CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {
    System.out.println("B come in");
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "playB";
});

CompletableFuture<String> result = playA.applyToEither(playB, f -> {
    return f + " is winner";
});
System.out.println(Thread.currentThread().getName()+"\t"+"----"+result.join());

5. Consolidate the calculation results

After the two CompletionStage tasks are completed, the results of the two tasks can finally be submitted to thenCombine for processing+
Wait for the first to complete, and wait for other branch tasks
thenCombine

code Standard Edition

CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "\t ----start-up");
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 10;
});

CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "\t ----start-up");
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 20;
});
CompletableFuture<Integer> completableFuture = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
    System.out.println("---Two values merge");
    return x + y;
});
System.out.println(completableFuture.join());

code expression (combined version)

CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t ----start-up");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 10;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t ----start-up");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 20;
        }),(x,y)->{
            System.out.println("---Two values merge");
            return x + y;
        });

//        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
//            System.out.println(Thread.currentThread().getName() + "\t -- start");
//            try {
//                TimeUnit.SECONDS.sleep(1);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            return 20;
//        });
//        CompletableFuture<Integer> completableFuture = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
//            System.out.println("--- combination of two values");
//            return x + y;
//        });
        System.out.println(completableFuture.join());
    }

3.java lock

From the easy optimistic lock and pessimistic lock

Pessimistic lock:

  • I think that when I use data, there must be other threads modifying the data. Therefore, I will lock the data first to ensure that the data will not be modified by other threads.
  • The synchronized keyword and the implementation class of Lock are pessimistic locks

Optimistic lock

  • Think that no other thread will modify data or resources when using data, so as not to add locks.
  • In Java, it is realized by using lock free programming. It is only to judge whether other threads have updated the data before updating the data.
  • If the data is not updated, the current thread successfully writes the modified data.
  • If the data has been updated by other threads, different operations will be performed according to different implementation methods, such as abandoning modification, retrying lock grabbing, etc

Demonstrate the lock operation case through 8 cases to see what we are locking

Lock related 8 cases demo code

class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("----sendEmail");
    }
    public synchronized void sendSMS(){
        System.out.println("----sendSMS");
    }
    public void Hello(){
        System.out.println("---hello");
    }
}
/**
 * Topic: talk about your understanding of multithreaded locks, 8 lock case descriptions
 * Pithy formula: thread operation resource beauty
 * 8 Lock case description:
 * 1.Standard access has two threads, ab. would you like to print mail or SMS mail first
 * 2.sendEmail The method clock is added and suspended for 3 seconds. Would you like to print the email or SMS email first
 * 3.Add a normal Hello method. Would you like to print the mail first or hello hello
 * 4.I have two mobile phones. Would you like to print email or SMS first
 * 5.There are two static synchronization methods. There is one mobile phone. Would you like to print email or SMS email first
 * 6.There are two static synchronization methods. There are two mobile phones. Would you like to print email or SMS email first
 * 7.There is one static synchronization method, one common synchronization method, and one mobile phone. Would you like to print email or SMS first
 * 8.There is one static synchronization method, one common synchronization method, and two mobile phones. Would you like to print email or SMS first
 *
 * Summary:
 * 1-2
 *  If there are multiple synchronized methods in an object, at a certain time, only one thread can call one of the synchronized methods,
 * Other threads can only wait. In other words, at a certain time, only one thread can access these synchronized methods
 * The lock is the current object this. After being locked, other threads cannot enter other synchronized methods of the current object
 * 3-4
 * After adding a common method, it is found that it has nothing to do with synchronization lock
 * When two images are replaced, the same lock is not locked, and the situation immediately changes,
 *
 * 5-6 After changing to the static synchronization method, the situation changes again
 * There are some differences in the contents of the three synchronized locks:
 * For common synchronization methods, the lock is the current instance object, usually this. For specific mobile phones, all common synchronization methods use the same lock - > instance object itself
 * For static synchronization methods, the lock is the only template of the Class object of the current Class, such as Phone.class
 * For the synchronized method block, the locked object is the object within the synchronized bracket
 *
 * 7-8
 * When a thread attempts to access the synchronization code, it must first obtain the lock. When it exits normally or throws an exception, it must release the lock.
 * All common synchronization methods use the same lock. The instance object itself is the specific instance object itself from new. this
 * That is to say, if a common synchronization method of an instance object acquires a lock, other common synchronization methods of the instance object must wait for the lock acquiring method to release the lock before acquiring the lock.
 * All static synchronization methods use the same lock. The class object itself is the only template class we mentioned
 * The specific instance object this and the unique template class are two different objects, so there will be no race condition between the static synchronization method and the common synchronization method
 * However, once a static synchronization method acquires a lock, other static synchronization methods must wait for the method to release the lock before acquiring the lock.
 * */
public class Lock8Demo {
    public static void main(String[] args) { //Entrance to all programs
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(phone::,"a").start();
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(phone2::,"b").start();
    }
}

synchronized has three application modes:

The cases of 8 kinds of locks are actually reflected in 3 places:
  • Acting on the instance method, the current instance is locked, and the lock of the current instance must be obtained before entering the synchronization code;

    public synchronized void sendEmail(){
    
  • It acts on the code block and locks the objects configured in parentheses.

    synchronized (this){}
    
  • Acting on static methods, the current class is locked, and the lock of the current class object must be obtained before entering the synchronization code;

    public static synchronized void sendSMS(){}
    

Analyzing the implementation of synchronized from the perspective of bytecode

Decompilation of javap -C **.class file
-C disassembly of code
If you need more information
  • Decompilation of javap -v **.class file
  • -V -verbose output additional information (including line number, local variable table, disassembly and other details)+
synchronized code block
Decompilation of javap -C***.class file
Decompile
 javap -c .\LockSyncDemo.class
Compiled from "LockSyncDemo.java"
public class locks.LockSyncDemo {
  java.lang.Object object;

  public locks.LockSyncDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: new           #2                  // class java/lang/Object
       8: dup
       9: invokespecial #1                  // Method java/lang/Object."<init>":()V
      12: putfield      #3                  // Field object:Ljava/lang/Object;
      15: return

  public void m1();
    Code:
       0: aload_0
       1: getfield      #3                  // Field object:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter
       7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: ldc           #5                  // String ----hello synchronized code block
      12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      15: aload_1
      16: monitorexit
      17: goto          25
      20: astore_2
      21: aload_1
      22: monitorexit
      23: aload_2
      24: athrow
      25: return
    Exception table:
       from    to  target type
           7    17    20   any
          20    23    20   any

  public static void main(java.lang.String[]);
    Code:
       0: return
}
synchronized code block
  • The implementation uses the monitorenter and monitorexit directives
Must it be one entry and two exit s?
  • In general, one entry corresponds to two exit s

  • extreme

    m1 method adds an exception

synchronized general synchronization method
Decompilation of javap -C***.class file
Decompile
javap -c .\LockSyncDemo.class
Compiled from "LockSyncDemo.java"
public class locks.LockSyncDemo {
public locks.LockSyncDemo();
Code:
0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: return

public synchronized void m2();
Code:
0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc           #3                  // String ----hello synchronized code block
5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return

public static void main(java.lang.String[]);
Code:
0: return
}
synchronized general synchronization method

Calling the instruction will check the ACC of the method_ Whether the synchronized access flag is set. If it is set, the execution thread will hold the monitor lock first, then execute the method, and finally release the monitor when the method completes (whether it completes normally or not)

synchronized static synchronization method
Decompilation of javap -C***.class file
Decompile
javap -c .\LockSyncDemo.class
Compiled from "LockSyncDemo.java"
public class locks.LockSyncDemo {
public locks.LockSyncDemo();
Code:
0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: return

public synchronized void m2();
Code:
0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc           #3                  // String ----hello synchronized code block m2
5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return

public synchronized void m3();
Code:
0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc           #5                  // String ----hello synchronized code block m3
5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return

public static void main(java.lang.String[]);
Code:
0: return
}
synchronized static synchronization method
  • ACC_ STATIC, ACC_ The synchronized access flag distinguishes whether the method is a static synchronization method

What is the decompilation synchronized lock

What is pipe monitor
Tube

Understand through the C underlying primitive

In HotSpot virtual machine, monitor is implemented by ObjectMonitor
Interpretation of the above C + + source code
  • ObjectMonitor.java→ObjectMonitor.cpp-→objectMonitor,hpp
  • objectMonitor.hpp
  • Each object is born with an object monitor
  • Each locked object will be associated with Monitor

Fair lock and unfair lock

Demonstrate fair and unfair phenomena from the ReentrantLock ticket selling demo

class Ticket //In the resource category, the museum plans to sell 50 tickets by 3 ticket sellers
{
    private int number = 50;
    ReentrantLock lock = new ReentrantLock(true);

    public void sale() {
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "Selling No: \t" + (number--) + "\t Remaining:" + number);
            }
        } finally {
            lock.unlock();
        }

    }
}

public class SaleTicketDemo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{for (int i = 0; i < 55; i++) ticket.sale();},"a").start();
        new Thread(()->{for (int i = 0; i < 55; i++) ticket.sale();},"b").start();
        new Thread(()->{for (int i = 0; i < 55; i++) ticket.sale();},"c").start();
    }
}

What is fair lock / unfair lock?

Type of lockdescribe
Fair lockIt means that multiple threads acquire locks in the order of applying for locks. Here, it is similar to queuing to buy tickets. The first to buy tickets first and the later to queue at the end of the queue. This is fair. Lock = new reentrantl lock (true)// True means fair lock, first come, first served
Unfair lockIt means that the order in which multiple threads acquire locks is not in the order in which they apply for locks. It is possible that the threads that apply later may acquire locks prior to the threads that apply first. In a high concurrency environment, it may cause priority reversal or starvation (a thread cannot get locks all the time). Lock lock = new reentrantl lock (alse)// False indicates that the lock is not fair, and later users may obtain the lock first
Why are there fair locks and unfair locks
  • There is still a time difference between resuming the suspended thread and obtaining the real lock. From the perspective of the developer, the time difference is very small, but from the perspective of the CPU, the time difference is very obvious. Therefore, the unfair lock can make full use of the time slice of the CPU and minimize the idle state time of the CPU.
  • An important consideration in using multithreading is the overhead of thread switching. When an unfair lock is used, when one thread requests the lock to obtain the synchronization state and then releases the synchronization state, the thread that just released the lock is here
When to use fair locks and when to use unfair locks
  • For higher throughput, it is obvious that unfair locking is more appropriate, because it saves a lot of thread switching time, and the throughput will naturally increase;
  • Otherwise, we will use the fair lock, and everyone will use it fairly

Embedded AQS

Reentrant lock (also known as recursive lock)

explain

Reentrant lock, also known as recursive lock
It means that when the same thread acquires a lock in the outer method, the inner method of the thread will automatically acquire the lock (the premise is that the lock object must be the same object). It will not be blocked because it has been acquired but not released before.
If it is a recursive call method decorated with synchronized, the second entry of the program is blocked by itself. Isn't it a big joke that it is bound by itself.
Therefore, both reentrant lock and synchronized in Java are reentrant locks. One of the advantages of reentrant locks is that deadlocks can be avoided to a certain extent.

The four words "re-entry lock" are explained separately:

  • OK: Yes.
  • Heavy: again.
  • In: in.
  • Lock: synchronous lock.

Enter what

  • Enter the synchronization domain (i.e. code that synchronizes code blocks / methods or explicit lock locks)

in a word

  • Multiple processes in a thread can acquire the same lock, and can enter again with this synchronization lock.
  • You can obtain your own internal lock

Type of reentry lock

  • Implicit lock (i.e. the lock used by the synchronized keyword) is a reentrant lock by default

    Sync block

    Synchronization method

    public class ReEntryLockDemo {
        public synchronized void m1() {
            //It refers to a lock that can be repeatedly and recursively called. After the lock is used in the outer layer, it can still be used in the inner layer without deadlock. Such a lock is called a reentrant lock.
            System.out.println(Thread.currentThread().getName() + "\t-----come in");
            m2();
            System.out.println(Thread.currentThread().getName() + "\t-----end m1");
        }
    
        public synchronized void m2() {
            System.out.println(Thread.currentThread().getName() + "\t-----come in");
            m3();
            //        System.out.println(Thread.currentThread().getName() + "\t-----end m2");
        }
    
        public synchronized void m3() {
            System.out.println(Thread.currentThread().getName() + "\t-----come in");
        }
    
        public static void main(String[] args) {
            ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
            new Thread(reEntryLockDemo::m1,"t1").start();
        }
    
        private static void reEntryM1() {
            final Object object = new Object();
            new Thread(() -> {
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "\t-----Outer call");
                    synchronized (object) {
                        System.out.println(Thread.currentThread().getName() + "\t-----Middle level call");
                        synchronized (object) {
                            System.out.println(Thread.currentThread().getName() + "\t-----Inner layer call");
                        }
                    }
                }
            }, "t1").start();
        }
    }
    
  • Implementation mechanism of Synchronized reentry

    Each lock object has a lock counter and a pointer to the thread that holds the lock.
    When executing monitorenter If the counter of the target lock object is zero, it means that it is not held by other threads, Java The virtual opportunity sets the holding thread of the lock object as the current thread,I And its counter is incremented by 1
     If the counter of the target lock object is not zero, if the holding thread of the lock object is the current thread, then Java The virtual machine can increment its counter by 1, otherwise it needs to wait until the holding thread releases the lock.
    When executing monitorexit When, Java The virtual machine needs to decrement the counter of the lock object by 1. A counter of zero indicates that the lock has been released.
    

    Why can any object become a lock
    ​ objectMonitor.hpp

  • Explicit locks (i.e. locks) also have reentrant locks such as reentrant lock.

    new Thread(()->{
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t-----come in Outer call");
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"\t-----come in Inner layer call");
    
            }finally {
              //  lock.unlock();
            }
        }finally {
            lock.unlock();
        }
    },"t1").start();
    new Thread(()->{
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t-----come in Outer call");
            lock.lock();
        }finally {
            lock.unlock();
        }
    },"t2").start();
    

Deadlock and troubleshooting

What is it?

Deadlock refers to the phenomenon that two or more threads wait for each other due to contention for resources during execution. If there is no external interference, they will not be able to move forward. If the system resources are sufficient and the resource requests of the processes can be met, the possibility of deadlock occurrence is very low. Otherwise, they will fall into deadlock due to contention for limited resources.

Please write a deadlock code case

final Object objectA = new Object();
final Object objectB = new Object();
new Thread(() -> {
    synchronized (objectA) {
        System.out.println(Thread.currentThread().getName() + "\t Own A Lock, hope to obtain B lock");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (objectB) {
            System.out.println(Thread.currentThread().getName() + "\t Successfully obtained B lock");
        }
    }
},"A").start();
new Thread(() -> {
    synchronized (objectB) {
        System.out.println(Thread.currentThread().getName() + "\t Own B Lock, hope to obtain A lock");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (objectA) {
            System.out.println(Thread.currentThread().getName() + "\t Successfully obtained A lock");
        }
    }
},"B").start();

Operation results:

How to troubleshoot deadlocks

Pure command
  • jps -|
  • jstack process number
Graphic
  • jconsole

Write lock (exclusive lock) / read lock (shared lock)

SpinLock

No lock → exclusive lock → read / write lock → postmark lock

No lock → deflection lock → light weight lock → weight lock

4.LockSupport and thread interruption

content validity

LockSupport

Thread interrupt mechanism

Thread interrupt mechanism

From the interview questions of Ali ant financial

How to interrupt a running thread?
How to stop a running thread?

What is the interrupt mechanism?

First of all
A thread should not be forcibly interrupted or stopped by other threads, but should be stopped by the thread itself and decide its own fate. Therefore, thread stop, Thread. Suspend and thread.resume have been abandoned.
Secondly
In Java, there is no way to stop a thread immediately, but it is particularly important to stop a thread, such as canceling a time-consuming operation. Therefore, Java provides a negotiation mechanism for stopping threads -- interrupt, that is, interrupt identification negotiation mechanism. Interrupt is just a cooperative negotiation mechanism. Java does not add any syntax to interrupt. The process of interrupt needs to be implemented by the programmer. To interrupt a thread, you need to manually call the interrupt method of the thread. This method only sets the interrupt identifier of the thread object to true;
Then you need to write your own code to constantly detect the identity bit of the current thread. If it is true, it means that other threads request the thread to interrupt. At this time, you need to write your own code to realize what to do.
Each thread object has an interrupt identification bit, which is used to indicate whether the thread is interrupted; If the flag bit is true, it means interrupt; if it is false, it means no interrupt;
Set the identification bit of the thread to true by calling the interrupt method of the thread object; It can be called in other threads or in its own thread

Description of the three API methods related to interrupts

Large factory interview question interruption mechanism test site

How to stop and interrupt a running thread?
Implement the port through a volatile variable
static volatile boolean isStop = false;

public static void main(String[] args) {
    new Thread(()->{
        while(true){
            if(isStop){
                System.out.println(Thread.currentThread().getName()+"\t isStop Modified to: true,Program stop");
                break;
            }
            System.out.println("----hello volatile");
        }
    },"t1").start();
    try {
        TimeUnit.MILLISECONDS.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    new Thread(()->{
        isStop=true;
    },"t2").start();

}
Through AtomicBoolean
new Thread(()->{
    while(true){
        if(atomicBoolean.get()){
            System.out.println(Thread.currentThread().getName()+"\t isStop Modified to: true,Program stop");
            break;
        }
        System.out.println("----hello volatile");
    }
},"t1").start();
try {
    TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
    e.printStackTrace();
}
new Thread(()->{
    atomicBoolean.set(true);
},"t2").start();
Implemented by the interrupt api instance method of Thread class

The interrupt status is continuously monitored in the thread that needs to be interrupted. Once an interrupt occurs, the corresponding interrupt processing business logic stop thread is executed

 Thread t1 = new Thread(() -> {
     while(true){
         if(Thread.currentThread().isInterrupted()){
             System.out.println(Thread.currentThread().getName()+"\t isStop Modified to: true,Program stop");
             break;
         }
         System.out.println("----hello interrupt api");
     }
 }, "t1");
 t1.start();
 try {
     TimeUnit.MILLISECONDS.sleep(20);
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
t1.isInterrupted();
api
code
Instance method interrupt() has no return value

Source code analysis

Instance method isInterrupted, return Boolean value

Source code analysis

explain

Specifically, when interrupt() is called on a thread:
① If the thread is in a normal active state, the interrupt flag of the thread is set to true, that's all. The thread with the interrupt flag set will continue to run normally and will not be affected.
Therefore, interrupt() can not really interrupt the thread. It needs the cooperation of the called thread itself.
② If a thread is in a blocked state (such as sleep, wait, join, etc.), and the interrupt method of the current thread object is called in another thread, the thread will immediately exit the blocked state and throw an InterruptedException exception.

If the interrupt flag of the current thread is true, will the thread stop immediately?
public class InterruptDemo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + "\t" +
                            "Interrupt flag bit" + Thread.currentThread().isInterrupted() + "Program stop");
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();//Why call the interrupt method again at the exception
                    e.printStackTrace();
                }
                System.out.println("-----hello InterruptDemo3");
            }
        }, "t1");
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(t1::interrupt,"t2").start();
    }

}
/**
 * 1 Interrupt flag bit, default false
 * 2 t2 ----> Terminal negotiation is issued, t2 calls t1.interrupt(), interrupt flag bit
 * 3 The interrupt flag bit is true. Under normal conditions, the program stops, ^_^
 * 4 The interrupt flag bit is true. The exception is InterruptedException. The interrupt status will be cleared and the InterruptedException will be received. Interrupt flag bit: false
 * 5 In the catch block, you need to set the interrupt flag bit to true again, and call the stop program twice before it is ok
 * */
Summary:

Interrupt is just a negotiation mechanism. Modifying the interrupt identifier bit is just that. It is not an immediate stop interrupt

Static method Thread.interrupted(), talk about your understanding

The static method interrupted will clear the interrupt state (the passed parameter ClearInterrupted is true),
The instance method isInterrupted will not (the passed parameter Clearlnterrupted is false)

summary

Methods related to thread interruption:
Public void interrupt(), the interrupt() method is - an instance method
It notifies the target thread to interrupt, and only sets the interrupt flag bit of the target thread to true.
public boolean isInterrupted(), isInterrupted() method is also an instance method
It determines whether the current thread is interrupted (by checking the interrupt flag bit) and obtains the interrupt flag,
Public static Boolean interrupted(), static method interrupted() of thread class
After returning the true value of the interrupt status of the current thread (boolean type), the interrupt status of the current thread will be set to false. After this method is called, the interrupt flag bit of the current thread will be cleared
Status (set the interrupt flag to false), return the current value and reset it to false

What is LockSupport

Basic thread blocking primitives for creating locks and other synchronization classes, not constructors

Thread wait wake mechanism

3 ways to make threads wait and wake up

  1. Use the wait() method in the Object to make the thread wait, and use the notify() method in the Object to wake up the thread
  2. Use the wait() method of Condition in the JUC package to make the thread wait, and use the signal() method to wake up the thread
  3. The LockSupport class can block the current thread and wake up the specified blocked thread

The wait and notify methods in the Object class implement thread waiting and wake-up

/**
 *
 * Requirement: t1 thread waits for 3 seconds, and t2 thread wakes up t1 thread to continue working after 3 seconds
 *
 * 1 Normal procedure demonstration
 *
 * The following abnormal conditions:
 * 2 wait Method and notify method. After removing the synchronization code block, see the running effect
 *   2.1 Abnormal situation
 *   Exception in thread "t1" java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method)
 *   Exception in thread "t2" java.lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method)
 *   2.2 conclusion
 *   Object The wait, notify, and notifyAll methods in the class used for thread waiting and wake-up must be executed inside synchronized (the keyword synchronized must be used).
 *
 * 3 Put notify before the wait method
 *   3.1 The program has been unable to end
 *   3.2 conclusion
 *   wait first, then notify and notifyall methods. The waiting thread will be awakened, otherwise it cannot be awakened
 */
public class LockSupportDemo
{

    public static void main(String[] args)//Main method, the main thread is the entry of all programs
    {
        Object objectLock = new Object(); //Same lock, similar to resource class

        new Thread(() -> {
            synchronized (objectLock) {
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"\t"+"Wake up");
        },"t1").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            synchronized (objectLock) {
                objectLock.notify();
            }

            //objectLock.notify();

            /*synchronized (objectLock) {
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }*/
        },"t2").start();
    }
}
normal
public class LockSupportDemo
{
    public static void main(String[] args)//Main method, the main thread is the entry of all programs
    {
        Object objectLock = new Object(); //Same lock, similar to resource class

        new Thread(() -> {
            synchronized (objectLock) {
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"\t"+"Wake up");
        },"t1").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            synchronized (objectLock) {
                objectLock.notify();
            }
        },"t2").start();
    }
}
Exception 1
/**
 * Requirement: t1 thread waits for 3 seconds, and t2 thread wakes up t1 thread to continue working after 3 seconds
 * The following abnormal conditions:
 * 2 wait Method and notify method. After removing the synchronization code block, see the running effect
 *   2.1 Abnormal situation
 *   Exception in thread "t1" java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method)
 *   Exception in thread "t2" java.lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method)
 *   2.2 conclusion
 *   Object The wait, notify, and notifyAll methods in the class used for thread waiting and wake-up must be executed inside synchronized (the keyword synchronized must be used).
 */
public class LockSupportDemo
{

    public static void main(String[] args)//Main method, the main thread is the entry of all programs
    {
        Object objectLock = new Object(); //Same lock, similar to resource class

        new Thread(() -> {
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            System.out.println(Thread.currentThread().getName()+"\t"+"Wake up");
        },"t1").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            objectLock.notify();
        },"t2").start();
    }
}

The wait method and the notify method, both of which remove the synchronization code block

Exception 2
/**
 *
 * Requirement: t1 thread waits for 3 seconds, and t2 thread wakes up t1 thread to continue working after 3 seconds
 *
 * 3 Put notify before the wait method and execute it first. t1 notifies first, and t2 thread executes the wait method after 3 seconds
 *   3.1 The program has been unable to end
 *   3.2 conclusion
 *   wait first, then notify and notifyall methods. The waiting thread will be awakened, otherwise it cannot be awakened
 */
public class LockSupportDemo
{

    public static void main(String[] args)//Main method, the main thread is the entry of all programs
    {
        Object objectLock = new Object(); //Same lock, similar to resource class

        new Thread(() -> {
            synchronized (objectLock) {
                objectLock.notify();
            }
            System.out.println(Thread.currentThread().getName()+"\t"+"Notified");
        },"t1").start();

        //t1 notifies first, and t2 executes the wait method after 3 seconds
        try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            synchronized (objectLock) {
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"\t"+"Wake up");
        },"t2").start();
    }
}

Put notify before the wait method

The program cannot be executed and cannot wake up

summary

The wait and notify methods must be in the synchronization block or method and used in pairs

wait first and then notify

The wait post signal method in the Condition interface implements the wait and wake-up of the thread

normal

public class LockSupportDemo2
{
    public static void main(String[] args)
    {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println(Thread.currentThread().getName()+"\t"+"start");
                condition.await();
                System.out.println(Thread.currentThread().getName()+"\t"+"Awakened");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        },"t1").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            lock.lock();
            try
            {
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName()+"\t"+"Notified");
        },"t2").start();

    }
}

Exception 1

/**
 * Exception:
 * condition.await();And condition.signal(); Both triggered the IllegalMonitorStateException exception
 *
 * Reason: the premise of calling the thread waiting and wake-up methods in condition is that there must be a lock in the lock and unlock methods before calling
 */
public class LockSupportDemo2
{
    public static void main(String[] args)
    {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            try
            {
                System.out.println(Thread.currentThread().getName()+"\t"+"start");
                condition.await();
                System.out.println(Thread.currentThread().getName()+"\t"+"Awakened");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            try
            {
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"\t"+"Notified");
        },"t2").start();

    }
}

Remove lock/unlock

condition.await(); And condition signal(); Both triggered the IllegalMonitorStateException exception.

Conclusion: the thread waiting and wake-up methods in the call condition can be called correctly only when the lock and unlock pairs are inside

Exception 2

/**
 * Exception:
 * The program cannot run
 *
 * Reason: wait () before signal is OK, otherwise the thread cannot be awakened
 */
public class LockSupportDemo2
{
    public static void main(String[] args)
    {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            lock.lock();
            try
            {
                condition.signal();
                System.out.println(Thread.currentThread().getName()+"\t"+"signal");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        },"t1").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println(Thread.currentThread().getName()+"\t"+"Waiting to be awakened");
                condition.await();
                System.out.println(Thread.currentThread().getName()+"\t"+"Awakened");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        },"t2").start();

    }
}

signal before await

summary

Before the thread waiting and wake-up methods in Condtion, the lock needs to be acquired first

Be sure to wait before signal. Don't reverse it

park wait and unpark wake in LockSupport class

Block and wake up threads through park() and unpark(thread) methods

LockSupport is a basic thread blocking primitive used to create locks and other synchronization classes.

The LockSupport class uses a concept called permission to block and wake up threads. Each thread has a permission. The permission has only two values: 1 and 0. The default value is zero. The license can be regarded as a (0,1) Semaphore, but unlike Semaphore, the upper limit of the license accumulation is 1.

Main methods

block

park() /park(Object blocker)

Block the current thread / block the incoming concrete thread

awaken

unpark(Thread thread)

Wakes up the specified thread in a blocked state

code

Normal + no lock block requirements

public class LockSupportDemo3
{
    public static void main(String[] args)
    {
        //Normal use + no lock block required
Thread t1 = new Thread(() -> {
    System.out.println(Thread.currentThread().getName()+" "+"1111111111111");
    LockSupport.park();
    System.out.println(Thread.currentThread().getName()+" "+"2222222222222------end Awakened");
},"t1");
t1.start();

//Pause the thread for a few seconds
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName()+"   -----LockSupport.unparrk() invoked over");

    }
}

Previous errors wake up first and wait, LockSupport still supports

public class T1
{
    public static void main(String[] args)
    {
        Thread t1 = new Thread(() -> {
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---Wake up");
        },"t1");
        t1.start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---unpark over");
    }
}
Why can we break the original call order of wait/notify?

Because unpark obtains a voucher and then calls the park method, the voucher consumption can be justified, so it will not be blocked. After the voucher is issued, it can be unblocked.

Why is the thread blocked twice after waking up twice, but the end result is still blocked?

Because the maximum number of vouchers is 1, two consecutive calls to unpark and one call to unpark have the same effect. Only one voucher will be added. However, two calls to park need to consume two vouchers, which are insufficient and cannot be released.

5. JMM of Java Memory Model

Start with the interview questions of big factories

Do you know what the Java Memory Model JMM is?

JMM. And volatile. What is the relationship between them? (detailed explanation in the next chapter)

What are the features of JMM or what are its three major features?

Why does JMM exist and why does it appear? What are the roles and functions?

Have you ever understood the happens before principle?

Computer hardware storage system

The storage structure of the computer is from the local disk to the main memory to the CPU cache, that is, from the hard disk to the memory and to the CPU. Generally, the operation of the corresponding program is to check the data from the database to the memory, and then to the CPU for calculation

Because there are so many levels of cache (the speed of the CPU and the physical main memory is inconsistent), the CPU does not directly operate the memory, but first reads the data in the memory to the cache, and the read and write operations of the memory will cause inconsistent problems

The Java virtual machine specification attempts to define a java Memory Model (JMM) to shield the memory access differences of various hardware and operating systems, so as to achieve a consistent memory access effect for Java programs under various platforms. Deduce that we need to know JMM

Java Memory Model

JMM(Java Memory Model, abbreviated as JMM) itself is an abstract concept and does not really exist. It only describes a group of conventions or specifications. Through this group of specifications, it defines the read and write access modes of various variables in the program (especially multithreading) and determines when and how one thread writes to shared variables to become visible to another thread. The key technical points are around the atomicity of multithreading Visibility and order are expanded.

principle:

The key technical points of JMM are all around the atomicity, visibility and orderliness of multithreading

What can I do?

1 realize the abstract relationship between thread and main memory through JMM.

2 shield the memory access differences of various hardware platforms and operating systems to achieve consistent memory access effects for Java programs under various platforms.

Three characteristics under JMM specification

1. Visibility

Common shared variables in Java do not guarantee visibility because the timing of data modification being written into memory is uncertain, and "dirty reading" is likely to occur under multi thread concurrency. Therefore, each thread has its own working memory. The main memory copy of the variables used by the thread is saved in the working memory of the thread. All operations (reading, assignment, etc.) of the thread on the variables must be carried out in the working memory of the thread, And cannot directly read and write variables in the main memory. Different threads cannot directly access the variables in each other's working memory, and the transfer of variable values between threads needs to be completed through the main memory

Thread dirty read: if there is no visibility guarantee

2. Atomicity

It means that an operation is non interruptible, that is, in a multi-threaded environment, the operation cannot be interfered by other threads

3. Orderliness

For the code executed by a thread, we always habitually think that the code is always executed from top to bottom in an orderly manner. However, in order to provide performance, compilers and processors usually reorder instruction sequences. Instruction rearrangement can ensure that the serial semantics are consistent, but there is no obligation to ensure that the semantics among multiple threads are consistent, that is, it can generate "dirty reading". In short, when two or more lines of unrelated code are executed, it is possible that the first one is not executed first, and it is not necessarily executed from top to bottom, and the execution order will be optimized.

In the single thread environment, ensure that the final execution result of the program is consistent with the result of the code sequential execution. When the processor performs reordering, it must consider the data dependency between instructions. In a multi-threaded environment, threads execute alternately. Due to the existence of compiler optimization and reordering, it is impossible to determine whether the variables used in the two threads can ensure consistency, and the result is unpredictable

Multi thread reading and writing of variables under JMM specification

Read process

Summary:

  • All shared variables we define are stored in physical main memory
  • Each thread has its own independent working memory, which stores a copy of the variable used by the thread (a copy of the variable in the main memory)
  • All operations of a thread on shared variables must be performed in the thread's own working memory first and then written back to the main memory. It is not allowed to read and write directly from the main memory (it is not allowed to skip levels)
  • Different threads cannot directly access the variables in the working memory of other threads, and the transfer of variable values between threads needs to be carried out through the main memory (peers cannot access each other)

The happens before principle of multithread first occurrence under JMM specification

In JMM, if the result of one operation requires the visibility or code reordering of another operation, there must be a happens before principle between the two operations. Logical precedence.

Description of the principle of first occurrence

If all the orderliness in the JAVA memory model is only achieved by volatile and synchronized, many operations will become very verbose, but we did not notice this when writing Java Concurrent code.

We do not add volatile and synchronized to complete the program at any time, everywhere and time. This is because there is a "happens before" principle and rule under the JMM principle in the Java language

This principle is very important: it is a very useful means to judge whether there is competition in data and whether threads are safe. Relying on this principle, we can solve all the problems of whether there may be conflict between two operations in a concurrent environment through a few simple rules in a package, without falling into the bitter underlying compilation principle of the Java memory model.

x. y case description

General principles of happens before

  • If an operation happens before another operation, the execution result of the first operation will be visible to the second operation, and the execution order of the first operation is ahead of the second operation.
  • The existence of the happens before relationship between the two operations does not mean that they must be executed in the order established by the happens before principle. If the execution result after reordering is consistent with the result executed according to the happens before relationship, this reordering is not illegal.
    • 1+2+3 = 3+2+1
    • Duty: Monday, Wednesday, Tuesday, Thursday, Thursday. If you have something to change, you can

Article 8 of happens before

1. Order rule

In a thread, according to the code sequence, the operation written in the front occurs first in the operation written in the back;

The result of the previous operation can be obtained by subsequent operations. To be clear, the previous operation assigns the variable x to 1, and the latter operation must know that X has become 1

2. Locking rules:

An unLock operation occurs first after the lock operation on the same lock ((the "later" here refers to the time sequence);

public class HappenBeforeDemo
{
    static Object objectLock = new Object();

    public static void main(String[] args) throws InterruptedException
    {
        //For the same lock objectLock, threadA must unlock the same lock before B can obtain the lock. A occurs before B
        synchronized (objectLock)
        {

        }
    }
}

3.volatile variable rules:

The write operation to a volatile variable first occurs after the read operation to the variable. The previous write is visible to the subsequent read. The "later" here also refers to the time sequence.

4. Transmission rules:

If operation A occurs first in operation B and operation B occurs first in operation C, it can be concluded that operation A occurs first in operation C;

5. Thread start rule:

The start() method of the Thread object precedes each action of this Thread

6. Thread interruption rule:

The call to the thread interrupt() method occurs first when the code of the interrupted thread detects the occurrence of an interrupt event;

Whether an interrupt occurs can be detected through Thread.interrupted()

7. Thread termination rule:

All operations in the thread occur in the termination detection of this thread. We can detect whether the thread has terminated execution by means of whether the Thread::join() method ends and the return value of Thread::isAlive().

8. Object finalizer rule:

The initialization of an object (the end of constructor execution) occurs at the beginning of its finalize() method

You cannot call the finalized() method before the object is initialized

Happens before - small summary

In the Java language, the semantics of happens before is essentially a kind of visibility
Ahappens beforeb means that what happened in A is visible to B, no matter whether event A and event B occur in the same thread
The design of JMM is divided into two parts:
One part is provided for our programmers, that is, the happens before rule. It explains a strong memory model to our programmers in an easy to understand way. As long as we understand the happens before rule, we can write concurrent and safe programs.
The other part is implemented for the JVM. In order to reduce the constraints on the compiler and processor as much as possible to improve the performance, JMM does not require the program execution results, that is, it allows optimization and reordering. We only need to pay attention to the former, that is, we can understand the happens before rule. Other complicated contents are handled by the JMM specification combined with the operating system. We only need to write the code.

case

8.volatile and Java Memory Model

1. Variables modified by volatile have two characteristics:

  • visibility
  • Orderliness

2. Memory semantics of volatile

  • When writing a volatile variable, JMM will immediately flush the shared variable value in the local memory corresponding to the thread back to the main memory.
  • When reading a volatile variable, JMM will set the local memory corresponding to the thread as invalid and directly read the shared variable from the main memory
  • Therefore, the write memory semantics of volatile are directly flushed into the main memory, and the read memory semantics are directly read from the main memory.

3. Memory barrier (key)

1. Life case

  • Without control, the order is difficult to maintain
  • Set rules and prohibit disorder

2. What is the memory barrier

Memory barrier (also known as memory barrier, memory barrier, barrier instruction, etc.) is a kind of synchronization barrier instruction, which is a synchronization point in the operation of random access to memory by the CPU or compiler, so that all read and write operations before this point can be executed before the operation after this point) to avoid code reordering. The memory barrier is actually a JVM instruction. The rearrangement rules of the Java memory model require the Java compiler to insert specific memory barrier instructions when generating JVM instructions. Through these memory barrier instructions, volatile realizes the visibility and order in the Java memory model, but volatile cannot guarantee atomicity.

All write operations before the memory barrier must be written back to the main memory. All read operations after the memory barrier can obtain the latest results of all write operations before the memory barrier (visibility is achieved).

Therefore, when reordering, it is not allowed to reorder the instructions after the memory barrier to the instructions before the memory barrier. In a word: the writing of a volatile domain happens before any subsequent reading of the volatile domain, which is also called reading after writing.

Rough classification:

Read barrier:

Insert the read barrier before the read instruction to invalidate the cached data in the working memory or CPU cache, and return to the main memory to obtain the latest data

Write barrier:

Insert a write barrier after the write instruction to force the data in the write buffer to be flushed back to the main memory

to subdivide:

c + + source code analysis:

Unsafe.class

orderAccess.hpp

Unsafe.cpp

orderAccess_ linux_ _x86.inline.hpp:

What do the four barriers mean

3. What is the guarantee of order?

Prohibit rearrangement through memory barrier
Above description

1 reordering may affect the execution and implementation of the program. Therefore, we sometimes want to tell the JVM not to "act smart" to reorder me. I don't need to reorder here. I listen to the master.
2 for compiler reordering, JMM will prohibit specific compiler reordering according to the reordering rules.
3 for processor reordering, the Java compiler inserts a memory barrier instruction at the appropriate position of the generated instruction sequence to prohibit specific types of processor reordering.

4. volatile variable rule of happens before 7

5. JMM divides the memory barrier insertion strategy into four rules:

  • write
    • Insert a StoreStore barrier before each volatile write operation
    • Insert a StoreLoad barrier after each volatile write operation
  • read
    • Insert a LoadLoad barrier after each volatile read operation
    • Insert a LoadStore barrier after each volatile read operation

4.volatile characteristics

1. Ensure visibility

Ensure the visibility of different threads when operating on this variable, that is, once the variable is changed, all threads are immediately visible

public class VolatileSeeDemo
{
    static          boolean flag = true;       //No volatile, no visibility
    //static volatile boolean flag = true;       // Volatile is added to ensure visibility

    public static void main(String[] args)
    {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"\t come in");
            while (flag)
            {

            }
            System.out.println(Thread.currentThread().getName()+"\t flag Modified to: false,sign out.....");
        },"t1").start();

        //After pausing for 2 seconds, let the main thread modify the flag value
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        flag = false;

        System.out.println("main Thread modification completed");
    }
}
  • Without volatile and visibility, the program cannot be stopped
  • volatile is added to ensure visibility and the program can be stopped
thread  t1 Why can't the main thread be seen in main Modified as: false of flag Value of?
 
Problem: possible:
1. Main thread modified flag After that, it is not flushed to the main memory, so t1 Thread cannot see.
2. The main thread will flag Flushed to main memory, but t1 Always read from your own working memory flag The value of is not updated in the main memory flag The latest value.
 
Our appeal:
1.After the copy in the working memory is modified in the thread, it is flushed to the main memory immediately;
2.Each time a shared variable is read in the working memory, it will be re read in the main memory and then copied to the working memory.
 
solve:
use volatile The above effect can be achieved by modifying the shared variable volatile The modified variables have the following characteristics:
1. When reading in the thread, the latest value of the shared variable will be read from the main memory every time, and then copied to the working memory
2. The copy of the variable in the working memory is modified in the thread, and it will be flushed to the main memory immediately after modification

2. Read and write process of volatile variable

Atomic operations between 8 working memory and main memory defined in Java Memory Model read → load → use → assign → store → write → lock → unlock

read: acts on the main memory, transfers the value of the variable from the main memory to the working memory, and the main memory to the working memory

load: acts on the working memory, and puts the variable value transferred from the main memory by read into the working memory variable copy, that is, data loading

use: acts on the working memory, passing the value of the copy of the working memory variable to the execution engine. The JVM will execute this operation whenever it encounters a bytecode instruction that needs the variable

assign: acts on the working memory, assigns the value received from the execution engine to the working memory variable, and executes this operation whenever the JVM encounters a bytecode instruction that assigns a variable

store: acts on the working memory and writes the value of the assigned working variable back to the main memory

write: it acts on the main memory and assigns the value of the variable transferred from store to the variable in the main memory. Since the above can only guarantee the atomicity of a single instruction, and the combinatorial atomicity of multiple instructions is guaranteed without large-area locking, the JVM provides two other atomic instructions:

lock: acts on the main memory and marks a variable as a thread exclusive state. It is only locked when writing, which is the process of locking the variable.

unlock: acts on the main memory to release a variable in the locked state before it can be occupied by other threads

3. No atomicity

1. Compound operations of volatile variables (such as i + +) are not atomic
class MyNumber
{
    volatile int number = 0;

    public void addPlusPlus()
    {
        number++;
    }
}

public class VolatileNoAtomicDemo
{
    public static void main(String[] args) throws InterruptedException
    {
        MyNumber myNumber = new MyNumber();

        for (int i = 1; i <=10; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    myNumber.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        
        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "\t" + myNumber.number);
    }
}

From the perspective of i + + bytecode

Atomicity means that an operation is non interruptible. Even in a multi-threaded environment, once an operation starts, it will not be affected by other threads.
public void add()
{
        i++; //It does not have atomicity. This operation is to read the value first and then write back a new value, which is equivalent to the original value plus 1. It is completed in three steps
 }
If the second thread reads while the first thread reads the old value and writes back the new value i The second thread will see the same value together with the first thread,
And performs the operation of adding 1 to the same value, which also causes thread safety failure. Therefore, for add Method must use synchronized Decorated to ensure thread safety.

In the multithreading environment,"Data calculation"and"Data assignment"The operation may occur multiple times, that is, the operation is not atomic. If the data is loaded, if the main memory count After the variable is modified, since the value in the working memory of the thread has been loaded before, no corresponding change will be made to the change operation, that is, the variables in the private memory and the public memory are not synchronized, resulting in inconsistent data
 about volatile Variables, JVM It only ensures that the value loaded from the main memory to the working memory of the thread is up-to-date, that is, the data is up-to-date when loaded.
thus it can be seen volatile The solution is to solve the visibility problem of variable reading, but atomicity cannot be guaranteed. Locking synchronization must be used for scenarios where multiple threads modify shared variables
2. Reading and assigning a common variable

When thread 1 initiates the read operation on the main memory object to the first set of write operations, thread 2 may initiate the second set of operations on the main memory object at any time

3. Since this modification is visible, why can't we guarantee atomicity?

volatile mainly deals with some of the instructions

want use(use)A variable is required load(Load) must be loaded from the main memory read(Reading) so that the visibility of reading is solved. 
The write operation is to assign and store Related(stay assign(assignment)Later (required) store(storage)). store(storage)after write(write in). 
That is to say, when assigning a value to a variable, a series of related instructions directly write the variable value to the main memory.
In this way, the memory visibility is achieved by directly accessing from the main memory during use, and directly writing back to the main memory after assignment. Pay attention to the clearance of the blue frame...... o(╥﹏╥)o

4. Read and assign a volatile variable

 read-load-use and assign-store-write Become two inseparable atomic operations, but in use and assign There is still a very small vacuum period between them. It is possible that the variable will be read by other threads, resulting in write loss once...o(╥﹏╥)o
 However, the values of the variables in the main memory and the variables in any working memory are equal at any time point. This feature leads to: volatile Variables are not suitable for operations that depend on the current value, such as i = i + 1; i++;So, it depends on the characteristics of visibility volatile Where can it be used? usually volatile Used to save a state boolean value or int Value.
<In depth understanding Java Virtual machine mentions:
5. Interview answer:

Bytecode of JVM, i + + is divided into three steps, and asynchronous non atomic operation (i + +) during the gap period

4. Command rearrangement prohibited

Reorder
 Reordering is a means by which the compiler and processor reorder the instruction sequence in order to optimize the program performance. Sometimes, the sequence of program statements will be changed
 There is no data dependency and reordering is allowed;
There is a data dependency, and reordering is prohibited
 But the rearranged instructions must not change the original serial semantics! This must be taken into account in concurrent design!
    
Classification and execution process of reordering

Reorder
 Reordering is a means by which the compiler and processor reorder the instruction sequence in order to optimize the program performance. Sometimes, the sequence of program statements will be changed
 There is no data dependency and reordering is allowed;
There is a data dependency, and reordering is prohibited
 But the rearranged instructions must not change the original serial semantics! This must be taken into account in concurrent design!
    
Classification and execution process of reordering
Before rearrangementAfter rearrangement
int a = 1; //1
int b = 20; //2
int c = a + b; //3
int b = 20; //1
int a = 1; //2
int c = a + b; //3
Conclusion: the compiler adjusts the order of statements, but does not affect the final result of the program.Reorder OK
There is a data dependency, and reordering is prohibited===> Reordering occurs, which will result in different program running results.
When the compiler and the processor reorder, they will abide by the data dependency and will not change the execution of the two operations with dependency,However, the data between different processors and different threads will not be considered by the compiler and the processor. It will only work in the single processor and single thread environment. In the following three cases, as long as the execution order of the two operations is reordered, the execution result of the program will be changed.

1. The underlying implementation of volatile is through the memory barrier

When the first operation is a volatile read, no matter what the second operation is, it cannot be reordered. This operation ensures that the operation after volatile read will not be rearranged before volatile read.

When the second operation is written as volatile, no matter what the first operation is, it cannot be reordered. This operation ensures that operations before volatile write will not be rearranged after volatile write.

When the first operation is volatile write and the second operation is volatile read, rearrangement is not allowed.

Insertion of four barriers

  • Insert a StoreStore barrier before each volatile write operation
    • The StoreStore barrier can ensure that all normal write operations before the volatile write have been flushed to the main memory.
  • Insert a StoreLoad barrier after each volatile write operation
    • The role of the StoreLoad barrier is to avoid the reordering of volatile writes and subsequent volatile read / write operations
  • Insert a LoadLoad barrier after each volatile read operation
    • The LoadLoad barrier is used to prevent the processor from reordering the volatile reads above with the normal reads below.
  • Insert a LoadStore barrier after each volatile read operation
    • The LoadStore barrier is used to prevent the processor from reordering the volatile reads above and the normal writes below.
//Simulate a single thread, what order to read? In what order?
public class VolatileTest {
    int i = 0;
    volatile boolean flag = false;
    public void write(){
        i = 2;
        flag = true;
    }
    public void read(){
        if(flag){
            System.out.println("---i = " + i);
        }
    }
}

5. How to use volatile correctly

1. Single assignment is allowed, but assignment with compound operation is not allowed (i + +)
volatile int a = 10
volatile boolean flag = false
2. Status flag to judge whether the business is ended
/**
 *
 * Use: as a Boolean status flag, it is used to indicate that an important one-time event has occurred, such as completion of initialization or end of task
 * Reason: the state flag does not depend on any other state in the program, and there is usually only one state transition
 * Example: judge whether the business is finished
 */
public class UseVolatileDemo
{
    private volatile static boolean flag = true;

    public static void main(String[] args)
    {
        new Thread(() -> {
            while(flag) {
                //do something......
            }
        },"t1").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            flag = false;
        },"t2").start();
    }
}
3. Low cost read-write lock strategy
public class UseVolatileDemo
{
    /**
     * Use: when reading is far more than writing, use internal lock and volatile variable to reduce synchronization overhead
     * Reason: use volatile to ensure the visibility of read operations; Using synchronized to ensure the atomicity of composite operations
     */
    public class Counter
    {
        private volatile int value;

        public int getValue()
        {
            return value;   //Using volatile to ensure the visibility of read operations
              }
        public synchronized int increment()
        {
            return value++; //Using synchronized to ensure the atomicity of composite operations
               }
    }
}
4. Release of DCL double end lock

Question code:

public class SafeDoubleCheckSingleton
{
    private static SafeDoubleCheckSingleton singleton;
    //Privatization construction method
    private SafeDoubleCheckSingleton(){
    }
    //Double lock design
    public static SafeDoubleCheckSingleton getInstance(){
        if (singleton == null){
            //1. When multiple threads create objects concurrently, they will lock to ensure that only one thread can create objects
            synchronized (SafeDoubleCheckSingleton.class){
                if (singleton == null){
                    //Hidden danger: in a multithreaded environment, the object may be read by other threads before initialization due to reordering
                    singleton = new SafeDoubleCheckSingleton();
                }
            }
        }
        //2. After the object is created, execute getInstance() to return the created object directly without obtaining the lock
        return singleton;
    }
}

Single thread problem code

In a single thread environment (or under normal circumstances), the following operations will be performed at the "problem code" to ensure that the initialized instance can be obtained

Due to instruction reordering

Multithreading problem code

Hidden danger: in a multithreaded environment, the following operations will be performed at the "problem code". Due to reordering, 2,3 are out of order, and the result is that other threads get null instead of the initialized object

Solution 01

Modified with volatile

Interview questions, against the case of Mr. Zhou Zhiming, do you still have a method without adding volatile

Solution 02 - implemented by static internal classes

//Now, a better way is to use the static internal method
 
public class SingletonDemo
{
    private SingletonDemo() { }

    private static class SingletonDemoHandler
    {
        private static SingletonDemo instance = new SingletonDemo();
    }

    public static SingletonDemo getInstance()
    {
        return SingletonDemoHandler.instance;
    }
}

6. Summary

volatile visibility

volatile has no atomicity
volatile: no rearrangement
Write instruction

Read instruction

Why do we write a volatile keyword in java
Bytecode level

Add a memory barrier at the bottom of the system? How did the two connect?
What is the memory barrier

Memory barrier: a barrier instruction that enables the CPU or compiler to perform a sort constraint on the memory operations issued before and after the barrier instruction. Also called memory fence or fence instruction

What can a memory barrier do

Prevent reordering of instructions on both sides of the barrier
Add a barrier when writing data, and force the data of the thread's private working memory to be brushed back to the main physical memory
Add a barrier when reading data, and the data in the thread's private working memory will become invalid. Go back to the main physical memory to obtain the latest data

Four instructions of memory barrier

Insert a StoreStore barrier before each volatile write operation
Insert a StoreLoad barrier after each volatile write operation
Insert a LoadLoad barrier after each volatile read operation
Insert a LoadStore barrier after each volatile read operation

3 sentence summary
  • Operations before volatile writing are prohibited from being reordered after volatile
  • Operations after volatile reading are prohibited from being reordered before volatile
  • Volatile read after volatile write, no reordering

7.CAS

Atomic class

java.util.concurrent. atomic

Before CAS

Multithreading environment does not use atomic classes to ensure thread safety i + + (basic data type)

After using CAS

Multithreading environment uses atomic classes to ensure thread safety i + + (basic data type)

Similar to our optimistic lock

What is it?

explain

compareandswap Is an abbreviation of, which is translated into comparison and exchange in Chinese. It is a technology commonly used when implementing concurrent algorithms.
It contains three operands--Memory location, expected original value and updated value.
implement CAS When operating, compare the value of the memory location with the expected original value:
The processor will automatically update the position value to a new value if it matches,
If there is no match, the processor does not do anything, and multiple threads execute at the same time CAS Only one operation will succeed.

principle

CAS has 3 operands, location memory value V, old expected value A, and updated value B to be modified.
If and only if the old expected value A and the memory value v are the same, modify the memory value V to B, otherwise, do nothing or start again
When it retries again, this behavior becomes - spin!

CASDemo

AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2022)+"\t"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 2022)+"\t"+atomicInteger.get());

Hardware level assurance

CAS yes JDK Non blocking atomic operation provided, which ensures comparison by hardware-Updated atomicity.
It is non blocking and atomic, which means it is more efficient and guaranteed by hardware, which means it is more reliable.
CAS Yes CPU Atomic instructions for(cmpxchg instructions),There is no so-called data inconsistency problem, Unsafe Provided
CAS method( as compareAndSwapXXX)The underlying implementation is: CPU instructions cmpxchg. 
implement cmpxchg When instructing, it will judge whether the current system is a multi-core system. If so, it will lock the bus. Only one thread will
 The bus is locked successfully and will be executed after the locking is successful cas Operation, that is to say CAS The atomicity of is actually CPU Realizing exclusive,
Compared with starting synchronized For heavy locks, the exclusive time here is much shorter, so the performance will be better in the case of multithreading.

Source code analysis compareAndSet(int expect,int update)

CAS underlying principle? If you know, talk about your understanding of UnSafe

1.UnSafe

  1. Unsafe is the core class of CAS. Since Java methods can not directly access the underlying system, they need to be accessed through native methods. Unsafe is equivalent to a back door, which can directly operate the data in specific memory based on this class. Unsafe class exists in sun In the Misc package, its internal method operations can directly operate the memory like C pointers, because the execution of CAS operations in Java depends on the methods of the unsafe class. Note that all methods in the unsafe class are modified natively, that is, the methods in the unsafe class directly call the underlying resources of the operating system to perform the corresponding tasks

  2. The variable valueOffset indicates the offset address of the variable value in memory, because Unsafe obtains data according to the memory offset address.

  3. The variable value is decorated with volatile to ensure memory visibility among multiple threads.

2. We know that i + + threads are not safe. Atomicinteger. Getandincrease ()

The full name of CAS is compare and swap, which is a CPU concurrency primitive. Its function is to determine whether the value of a certain location in the memory is the expected value. If so, it will be changed to a new value. This process is atomic. AtomicInteger class mainly uses CAS (compare and swap) + volatile and native methods to ensure atomic operations, thus avoiding the high overhead of synchronized and greatly improving the execution efficiency.

The CAS concurrency primitive is embodied in the JAVA language, namely sun.misc Each method in the UnSafe class. Call the CAS method in the UnSafe class, and the JVM will help us implement the CAS assembly instruction. This is a function completely dependent on hardware, through which atomic operation is realized. It is emphasized again that CAS is a system primitive, which belongs to the category of operating system terminology. It is composed of several instructions and is used to complete a process of a certain function. The execution of the primitive must be continuous and not allowed to be interrupted during the execution. That is to say, CAS is an atomic instruction of the CPU and will not cause the so-called data inconsistency problem.

Suppose that thread A and thread B execute getAndAddInt operation at the same time (running on different CPU s respectively):

  1. The original value of the value in the AtomicInteger is 3, that is, the value of the AtomicInteger in the main memory is 3. According to the JMM model, thread A and thread B each hold A copy of the value of 3 to their respective working memory.
  2. Thread A gets the value 3 through getIntVolatile(var1, var2). At this time, thread A is suspended.
  3. Thread B also obtains the value 3 through the getIntVolatile(var1, var2) method. At this time, thread B is not suspended and executes the compareAndSwapInt method to compare the memory value with 3. The memory value successfully modified is 4. Thread B finishes the work and everything is OK.
  4. At this time, thread A recovers and executes the compareAndSwapInt method to compare. It is found that the value number 3 in its own hand is inconsistent with the value number 4 in the main memory, which indicates that the value has been modified by other threads first. Thread A fails to modify this time and can only read it again.
  5. Thread A re obtains the value value. Because the variable value is modified by volatile, thread A can always see the modification made by other threads. Thread A continues to execute compareAndSwapInt for comparison and replacement until it succeeds.

3. Bottom assembly

The method modified by native represents the underlying method

compareAndSwapInt in Unsafe class is a local method whose implementation is located in unsafe.cpp

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
// First find a way to get the address of the variable value in memory, and calculate the address of value according to the offset valueOffset
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
// Call the function cmpxchg in Atomic to compare and exchange, where the parameter x is the value to be updated and the parameter e is the value of the original memory
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

(Atomic::cmpxchg(x, addr, e)) == e;

cmpxchg

// Call the function cmpxchg in Atomic to compare and exchange, where the parameter x is the value to be updated and the parameter e is the value of the original memory
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

unsigned Atomic::cmpxchg(unsigned int exchange_value,volatile unsigned int* dest, unsigned int compare_value) {
    assert(sizeof(unsigned int) == sizeof(jint), "more work to do");
  /*
   * Call overloaded functions on different platforms according to the operating system type. During precompiling, the compiler will decide which platform to call overloaded functions*/
    return (unsigned int)Atomic::cmpxchg((jint)exchange_value, (volatile jint*)dest, (jint)compare_value);
}

Different cmpxchg overloaded functions will be called under different operating systems. This time, win10 system is used

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
  //Determine whether it is a multi-core CPU
  int mp = os::is_MP();
  __asm {
    //The three move instructions mean to move the following value to the previous register
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    //CPU primitive level, CPU triggered
    LOCK_IF_MP(mp)
    //Compare and exchange instructions
    //cmpxchg: i.e. "compare and exchange" instruction
    //dword: the full name is double word, which means two words, a total of four bytes
    //ptr: the full name is pointer, which is used in conjunction with dword, indicating that the accessed memory unit is a double word unit 
    //Compare the value (compare_value) in the eax register with the value in the [edx] double word memory cell,
    //If it is the same, the value (exchange_value) in the ecx register is stored in the [edx] memory unit
    cmpxchg dword ptr [edx], ecx
  }
}

By now, we should understand the mechanism of CAS real implementation, which is finally completed by the assembly instructions of the operating system

4. Summary

You just need to remember: CAS It is implemented by hardware to improve efficiency at the hardware level. The bottom layer is still handed over to hardware to ensure atomicity and visibility
 The implementation method is based on the assembly instructions of the hardware platform, and is implemented in the intel of CPU in(X86 On the machine),Assembly instructions are used cmpxchg Instructions. 
 
The core idea is to compare the values of the variables to be updated V And expected value E(compare),Equal will V Set the value of to the new value N(swap)If not, spin again.

Atomic reference

AtomicInteger is an atomic integer. Can there be other atomic types?

  • AtomicBook
  • AtomicOrder
  • . . .
@Getter
@ToString
@AllArgsConstructor
class User
{
    String userName;
    int    age;
}


public class AtomicReferenceDemo
{
    public static void main(String[] args)
    {
        User z3 = new User("z3",24);
        User li4 = new User("li4",26);

        AtomicReference<User> atomicReferenceUser = new AtomicReference<>();

        atomicReferenceUser.set(z3);
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
    }
}

CAS and spin lock, using CAS for reference

Spin lock

It means that the thread trying to acquire the lock will not block immediately, but will try to acquire the lock in a circular manner. When the thread finds that the lock is occupied, it will continuously judge the lock status in a circular manner until it is acquired. The advantage of this is to reduce the consumption of thread context switching, but the disadvantage is that the loop will consume CPU

CAS disadvantages

1. Long cycle time and high cost

We can see that when the getAndAddInt method is executed, there is a do while

If CAS fails, it will keep trying. If CAS is not successful for a long time, it may bring a lot of overhead to the CPU.

2. The ABA problem that has been raised???

CAS Will lead to:“ ABA Question ".
 
CAS An important premise of the algorithm implementation is to take out the data at a certain time in the memory and compare and replace it at the current time, then the time difference class will lead to changes in the data.
 
For example, a thread one From memory location V Take out A,At this time, another thread two Also from the memory A,And thread two You do something to change the value to B,
Then the thread two Again V The data of the position becomes A,At this time, the thread one conduct CAS The operation found that the memory is still A,Then the thread one Operation succeeded.
 
Although thread one of CAS The operation is successful, but it does not mean that there is no problem in this process.
public class ABADemo
{
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);

    public static void main(String[] args)
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        new Thread(() -> {
            //Pause thread for a while
            try { Thread.sleep( 500 ); } catch (InterruptedException e) { e.printStackTrace(); };            System.out.println(atomicInteger.compareAndSet(100, 2019)+"\t"+atomicInteger.get());
        },"t2").start();

        //Pause the thread for a while, and main completely waits for the ABA presentation above to complete.
        try { Thread.sleep( 2000 ); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("============The following are: ABA Problem solving=============================");

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t First version number:"+stamp);//1
            //Pause the thread for a while,
            try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 2 Minor version number:"+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 3 Minor version number:"+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t First version number:"+stamp);//1
            //Pause the thread for a while to obtain the initial value of 100 and the initial version number of 1. Deliberately pause for 3 seconds to allow the t3 thread to complete an ABA operation, causing problems
            try { Thread.sleep( 3000 ); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);
            System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
        },"t4").start();
    }
}

8. Atomic operation

atomic

  1. AtomicBoolean
  2. AtomicInteger
  3. AtomicIntegerArray
  4. AtomicIntegerFieldUpdater
  5. AtomicLong
  6. AtomicLongArray
  7. AtomicLongFieldUpdater
  8. AtomicMarkableReference
  9. AtomicReference
  10. AtomicReferenceArray
  11. AtomicReferenceFieldUpdater
  12. AtomicStampedReference
  13. DoubleAccumulator
  14. DoubleAdder
  15. LongAccumulator
  16. LongAdder

Reclassification

Basic type: atomic class

AtomicBoolean
AtomicInteger
AtomicLong
Introduction to common API s
public final int get() / get the current value
public final int getAndSet(int newValue) / / get the current value and set the new value
public final int getAndIncrement() / / get the current value and increase it automatically
public final int getAndDecrement() 1 / get the current value and subtract from it
public final int getAndAdd(int delta) 1 / get the current value and add the expected value
boolean compareAndSet(int expect, int update) / / if the input value is equal to the expected value, set the value as the input value (update) in an atomic manner
Case
public class AtomicIntegerDemo {
    public static final int SIZE = 50;

    public static void main(String[] args) throws InterruptedException
    {
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(SIZE);

        for (int i = 1; i <=SIZE; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <=1000; j++) {
                        myNumber.addPlusPlus();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }
        //Wait for all the above 50 threads to complete the calculation before obtaining the final value

        //Pause the thread for a few seconds
        //try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+"\t"+"result: "+myNumber.atomicInteger.get());
    }
}
class MyNumber
{
    AtomicInteger atomicInteger = new AtomicInteger();

    public void addPlusPlus()
    {
        atomicInteger.getAndIncrement();
    }
}

Array type: atomic class

AtomicIntegerArray
Atomicl ongArray
AtomicReferenceArray
Case

public class AtomicIntegerArrayDemo
{
public static void main(String[] args)
{
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});

    for (int i = 0; i <atomicIntegerArray.length(); i++) {
        System.out.println(atomicIntegerArray.get(i));
    }
    System.out.println();
    System.out.println();
    System.out.println();
    int tmpInt = 0;

    tmpInt = atomicIntegerArray.getAndSet(0,1122);
    System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));
    atomicIntegerArray.getAndIncrement(1);
    atomicIntegerArray.getAndIncrement(1);
    tmpInt = atomicIntegerArray.getAndIncrement(1);
    System.out.println(tmpInt+"\t"+atomicIntegerArray.get(1));
}

}

Reference type: atomic class

AtomicReference
@Getter
@ToString
@AllArgsConstructor
class User
{
    String userName;
    int    age;
}

public class AtomicReferenceDemo
{
    public static void main(String[] args)
    {
        User z3 = new User("z3",24);
        User li4 = new User("li4",26);

        AtomicReference<User> atomicReferenceUser = new AtomicReference<>();

        atomicReferenceUser.set(z3);
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
    }
}

Spinlock demo

/**
 * Topic: realizing a spin lock
 * Spin lock benefits: there is no wait like blocking in the loop comparison acquisition.
 *
 * The spin lock is completed through CAS operation. Thread A calls the myLock method first and holds the lock for 5 seconds. Thread B finds it after entering
 * Currently, A thread holds A lock, which is not null. Therefore, it can only wait by spinning until A releases the lock and then B grabs it.
 */
public class SpinLockDemo
{
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock()
    {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t come in");
        while(!atomicReference.compareAndSet(null,thread))
        {

        }
    }

    public void myUnLock()
    {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t myUnLock over");
    }

    public static void main(String[] args)
    {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            spinLockDemo.myLock();
            //Pause thread for a while
            try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
            spinLockDemo.myUnLock();
        },"A").start();
        //Pause the thread for A while to ensure that thread A starts and completes before thread B
        try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            spinLockDemo.myLock();
            spinLockDemo.myUnLock();
        },"B").start();

    }
}
AtomicStampedReference
  • Reference type atomic class with version number can solve ABA problem
  • How many times has it been modified
  • State stamp atomic reference

ABADemo

public class ABADemo
{
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);

    public static void main(String[] args)
    {
        abaProblem();
        abaResolve();
    }

    public static void abaResolve()
    {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t3 ----1st time stamp  "+stamp);
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
            System.out.println("t3 ----2nd time stamp  "+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("t3 ----3rd time stamp  "+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t4 ----1st time stamp  "+stamp);
            //Pause the thread for a few seconds
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
        },"t4").start();
    }

    public static void abaProblem()
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            atomicInteger.compareAndSet(100,20210308);
            System.out.println(atomicInteger.get());
        },"t2").start();
    }
}
AtomicMarkableReference
  • Atomic update reference type object with tag bit
  • The solution is to simplify the status stamp to true|false – similar to disposable chopsticks

State stamp (true/false) atomic reference

public class ABADemo
{
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
    static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100,false);

    public static void main(String[] args)
    {
        new Thread(() -> {
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
            System.out.println(Thread.currentThread().getName()+"\t"+"update ok");
        },"t1").start();

        new Thread(() -> {
            //Pause the thread for a few seconds
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            atomicInteger.compareAndSet(100,2020);
        },"t2").start();

        //Pause the thread for a few seconds
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(atomicInteger.get());

        System.out.println();
        System.out.println();
        System.out.println();

        System.out.println("============The following are: ABA Problem solving,Let us know how many times the reference variable has been changed midway=========================");
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"\t 1 Minor version number"+stampedReference.getStamp());
            //Deliberately pause for 200ms, and let the t4 thread get the same version number as t3
            try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }

            stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 2 Minor version number"+stampedReference.getStamp());
            stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 3 Minor version number"+stampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t =======1 Minor version number"+stamp);
            //Pause for 2 seconds, and let t3 complete the ABA operation first. See if you can modify it
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean b = stampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t=======2 Minor version number"+stampedReference.getStamp()+"\t"+stampedReference.getReference());
        },"t4").start();

        System.out.println();
        System.out.println();
        System.out.println();

        System.out.println("============AtomicMarkableReference I don't care how many times the reference variable has been changed, I only care whether it has been changed======================");

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t 1 Minor version number"+marked);
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            markableReference.compareAndSet(100,101,marked,!marked);
            System.out.println(Thread.currentThread().getName()+"\t 2 Minor version number"+markableReference.isMarked());
            markableReference.compareAndSet(101,100,markableReference.isMarked(),!markableReference.isMarked());
            System.out.println(Thread.currentThread().getName()+"\t 3 Minor version number"+markableReference.isMarked());
        },"t5").start();

        new Thread(() -> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t 1 Minor version number"+marked);
            //Pause the thread for a few seconds
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            markableReference.compareAndSet(100,2020,marked,!marked);
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference()+"\t"+markableReference.isMarked());
        },"t6").start();
    }
}

Object modify atomic class

  • AtomicIntegerFieldUpdater
    • The value of the int type field in the atomic update object
  • AtomicLongFieldUpdater
    • The value of the Long type field in the atomic update object
  • AtomicReferenceFieldUpdater
    • Atomic updates the value of the reference type field
Purpose of use

Manipulate certain fields within a non thread safe object in a thread safe manner

Use requirements

Updated object properties must use the public volatile modifier.

Because the object's attribute modification type atomic classes are abstract classes, you must use the static method newUpdater() to create an updater every time you use it, and you need to set the class and attribute you want to update.

Interviewer: where did you use volatile

AtomicReferenceFieldUpdater

Case
AtomicIntegerFieldUpdaterDemo
class BankAccount
{
    private String bankName = "CCB";//bank
    public volatile int money = 0;//Amount of money
    AtomicIntegerFieldUpdater<BankAccount> accountAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");

    //No lock + high performance, local minimally invasive
    public void transferMoney(BankAccount bankAccount)
    {
        accountAtomicIntegerFieldUpdater.incrementAndGet(bankAccount);
    }
}

/**
 * @auther zzyy
 * @create 2020-07-14 18:06
 * Manipulate certain fields of non thread safe objects in a thread safe manner.
 * Demand:
 * 1000 If an individual transfers one yuan to an account at the same time, the accumulated amount should be increased by 1000 yuan,
 * In addition to synchronized and CAS, it can also be implemented using AtomicIntegerFieldUpdater.
 */
public class AtomicIntegerFieldUpdaterDemo
{

    public static void main(String[] args)
    {
        BankAccount bankAccount = new BankAccount();

        for (int i = 1; i <=1000; i++) {
            int finalI = i;
            new Thread(() -> {
                bankAccount.transferMoney(bankAccount);
            },String.valueOf(i)).start();
        }

        //Pause (MS)
        try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(bankAccount.money);

    }
}
AtomicReferenceFieldUpdater
class MyVar
{
    public volatile Boolean isInit = Boolean.FALSE;
    AtomicReferenceFieldUpdater<MyVar,Boolean> atomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");


    public void init(MyVar myVar)
    {
        if(atomicReferenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE))
        {
            System.out.println(Thread.currentThread().getName()+"\t"+"---init.....");
            //Pause the thread for a few seconds
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+"---init.....over");
        }else{
            System.out.println(Thread.currentThread().getName()+"\t"+"------Another thread is initializing");
        }
    }


}


/**
 * Multithreads call the initialization method of a class concurrently. If it has not been initialized, the initialization will be executed. It is required to initialize only once
 */
public class AtomicIntegerFieldUpdaterDemo
{
    public static void main(String[] args) throws InterruptedException
    {
        MyVar myVar = new MyVar();

        for (int i = 1; i <=5; i++) {
            new Thread(() -> {
                myVar.init(myVar);
            },String.valueOf(i)).start();
        }
    }
}

Deep analysis of atomic operation enhancement class principle

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

Hot commodity like calculator, like number plus statistics, does not require real-time accuracy

A very large ist, all of which are int types. How to implement addition? Talk about the idea

Source code and principle analysis
framework

Principle (why LongAdder is so fast)

Official website description and Alibaba requirements

LongAdder is a subclass of Striped64

Striped64

Definition of some variables or methods in striped64

Cell

Why is LongAdder so fast

The bottom layer is a decentralized hotspot.

In depth analysis of source code interpretation

Summary:

When there is no competition, LongAdder operates on the same. Base like AtomicLong. When there is a competition relationship, it uses the method of dividing the whole into parts to disperse hot spots. It uses space for time, uses an array of cells, and splits -- values into this array of cells. When multiple threads need to operate on the value at the same time, they can hash the thread id to obtain the hash value, map the hash value to a subscript of the array ells according to the hash value, and then perform self increment operation on the value corresponding to the subscript. When all threads are finished, all values of array ells and base are added together as the final result.

longAdder.increment()

1,add(1L)

  1. Only update the base when there is no competition at first;
  2. If updating the base fails, create a new Cell [] array for the first time
  3. When multiple threads compete fiercely for the same Cell, Cell [] may need to be expanded
2,longAccumulate

longAccumulate input parameter description

Definition of some variables or methods in Striped64

Thread hash value: probe

general programme

The above code first assigns a hash Value, and then enter a for(;;)Spin, which is divided into three branches:
CASE1: Cell[]Array already initialized
CASE2: Cell[]Array not initialized(New for the first time)
CASE3: Cell[]Array initializing

Just about to initialize Cell [] array (new for the first time)

The Cell [] array has not been initialized. Try to occupy the lock and initialize the cells array for the first time

If the above conditions are executed successfully, the initialization and assignment of the array will be performed, Cell[] rs = new Cell[2]Indicates that the length of the array is 2,
rs[h & 1] = new Cell(x) Means to create a new Cell Elements, value yes x Value, the default is 1.
h & 1 Similar to our previous HashMap Commonly used calculation hash bucket index The algorithm is usually hash & (table.len - 1). with hashmap One meaning.

Bottom

Multiple threads try to modify CAS, and the thread that fails will go to this branch

This branch directly operates the base cardinality and adds the value to the base, that is, other threads are initializing and multiple threads are updating the base value.

The Cell array is no longer empty, and there may be Cell array expansion

Competition of multiple threads hitting one cell at the same time


The above code determines the current thread hash Whether the data position element pointed back is empty,
If empty, the Cell Put the data into the array and jump out of the loop.
If not, the loop continues.

It indicates that there is data in the array corresponding to the current thread and it has been reset hash Value,
By this time CAS The operation attempted to modify the value Values are accumulated x Operation, x The default is 1, if CAS If successful, it will directly jump out of the loop.

3,sum

sum() will accumulate the value and base in all Cell arrays as the return value. The core idea is to disperse the update pressure of one value of AtomicLong into multiple values, so as to degrade the update hotspot.

Why is the value of sum inaccurate in the case of concurrency

When sum is executed, there is no restriction on updating the base and cells (a fatal word). Therefore, LongAdder is not strongly consistent, it is ultimately consistent.

First of all, the local variable of sum finally returned is initially copied as a base. When it is finally returned, it is likely that the base has been updated. At this time, the local variable sum will not be updated, resulting in inconsistency. Second, the reading of the cell here is not guaranteed to be the last written value. Therefore, the sum method can obtain correct results without concurrency

Usage Summary
  • AtomicLong
    • Thread safety, allowing some performance loss, and can be used when high accuracy is required
    • Guaranteed accuracy, performance cost
    • AtomicLong is an atomic operation performed by multiple threads on a single hotspot value
  • LongAdder
    • It can be used when it is required to have a good performance under high parallel transmission and the accuracy of the value is not high
    • Guaranteed performance, accuracy cost
    • LongAdder means that each thread has its own slot. Each thread generally only performs CAS operations on the value in its own slot

summary

1,AtomicLong
  • principle
    • CAS + spin
    • incrementAndGet
  • scene
    • Global computation under low concurrency
    • AtomicLong can ensure the accuracy of counting in the case of concurrency. It solves the problem of concurrency security through CAS.
  • defect
    • Performance drops sharply after high concurrency
    • The spin of AtomicLong will become a bottleneck
    • The CAS operation of N threads modifies the value of the thread. Only one thread succeeds at a time, and the other N - 1 fails. The failed thread spins continuously until it succeeds. In such a case of a large number of failed spins, the cpu is hit high at once.
2,LongAdder
  • principle
    • CAS+Base+Cell array dispersion
    • Space for time and scattered hot data
  • scene
    • Global calculation under high parallel transmission
  • defect
    • If the calculation thread modifies the result after sum, the final result is not accurate enough

9.ThreadLocal

Introduction to ThreadLocal

Disgusting big factory interview questions

  • What is the data structure and relationship of ThreadLocalMap in ThreadL ocal?
  • Why is the key of ThreadL ocal a weak reference?
  • Do you know the ThreadLocal memory leak problem?
  • Why do you add the remove method in ThreadL ocal?

What is it?

What can I do

It mainly solves the problem that S allows each thread to bind its own value. By using the get() and set() methods, the default value is obtained or its value is changed to the value of the copy stored by the current thread, thus avoiding the thread safety problem. For example, in the 8-lock case we explained earlier, the resource class uses the same mobile phone, and multiple threads rob the same mobile phone for use. If there is no peace in the world?

api introduction

Forever Hello World

5 sales of houses. The group's top management only cares about the accurate statistics of the total sales volume. According to the statistics of the total sales volume, it is convenient for the group company to send some bonuses

  • All the heroes compete with each other
  • Code the above requirements have changed

How to deal with the above requirements???

code1

public class ThreadLocalDemo {
    public static void main(String[] args) throws InterruptedException {
        House house = new House();

        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                int size = new Random().nextInt(5) + 1;
                try {
                    for (int j = 1; j <= size; j++) {
                        house.saleHouse();
                        house.saleVolumeByThreadLocal();
                    }
                    System.out.println(Thread.currentThread().getName() + "\t" + "No. sales:" + house.saleVolume.get());
                } finally {
                    house.saleVolume.remove();
                }
            }, String.valueOf(i)).start();
        }
        ;

        //Pause (MS)
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "\t" + "How many sets are sold in total: " + house.saleCount);
    }
}

class House {
    int saleCount = 0;

    public synchronized void saleHouse() {
        ++saleCount;
    }

    ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);

    public void saleVolumeByThreadLocal() {
        saleVolume.set(1 + saleVolume.get());
    }
}

code2

public class ThreadLocalDemo2 {
    public static void main(String[] args) throws InterruptedException{
        MyData myData = new MyData();

        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        try
        {
            for (int i = 0; i < 10; i++) {
                threadPool.submit(() -> {
                    try {
                        Integer beforeInt = myData.threadLocalField.get();
                        myData.add();
                        Integer afterInt = myData.threadLocalField.get();
                        System.out.println(Thread.currentThread().getName()+"\t"+"beforeInt:"+beforeInt+"\t afterInt: "+afterInt);
                    } finally {
                        myData.threadLocalField.remove();
                    }
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }
    }
}
class MyData
{
    ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
    public void add()
    {
        threadLocalField.set(1 + threadLocalField.get());
    }
}

Through the above code summary

  • Because each Thread has its own instance copy, and this copy is only used by the current Thread itself
  • Since other threads are inaccessible, there is no problem of sharing among multiple threads.
  • Unified - sets the initial value, but each thread modifies this value independently of each other

in a word

How can we not compete

  • Join synchronized or Lock to control the access order of resources

  • Everyone is in good condition. There is no need to rob+

ThreadLocal source code analysis

Source code interpretation

Thread, ThreadLocal,ThreadLocalMap relationship

Thread and ThreadLocal

ThreadLocal and ThreadLocalMap

General summary of All three

Summary:

threadLocalMap is actually an Entry object with threadLocal instance as key and any object as value. When we assign a value to the threadLocal variable, we actually take the current threadLocal instance as the key and store the Entry with the value into this threadLocalMap

It can be roughly understood as: ThreadLocalMap literally means that this is a map that stores ThreadLocal objects (in fact, ThreadLocal is the Key), but it is a ThreadLocal object that has been wrapped in two layers:

The JVM internally maintains a thread version of Map < thread, t > (through the set method of ThreadLocal object, the ThreadLocal object itself is taken as a key and put into threadloadmap). When each thread needs to use this T, it uses the current thread to get it from the Map. In this way, each thread has its own independent variable, one for each person, and the competition condition is completely eliminated. It is an absolutely safe variable in the concurrent mode.

ThreadLocal memory leak problem

Start with Ali's test questions

What is a memory leak

The memory occupied by objects or variables that will no longer be used cannot be recycled, which is a memory leak.

Who caused the trouble?

  • why

  • What are strong reference, soft reference, weak reference and virtual reference?

    ThreadLocalMap literally means that this is a map that stores ThreadLocal objects (in fact, it is the Key), but it is a ThreadLocal object that has undergone two layers of packaging: (1) the first layer of packaging uses WeakReference < ThreadLocal <? > > Change ThreadLocal object into a weakly referenced object; (2) The second layer of wrapper defines a special class Entry to extend WeakReference

Each Thread object maintains a reference to ThreadLocalMap
ThreadLocalMap is the internal class of ThreadLocal, which is stored with Entry
When calling the set() method of ThreadLocal, you actually set the Value to ThreadLocalMap. The key is the ThreadLocal object, and the Value is the object passed in
When you call the get() method of ThreadLocal, you actually get the value from ThreadLocalMap. The key is the ThreadLocal object
ThreadLocal itself does not store values. It only serves as a key to let the thread obtain values from ThreadLocalMap. Because of this principle, ThreadLocal can realize "data isolation" and obtain the local variable values of the current thread, which is not affected by other threads

Why use weak references? How about not using it?

public void function01()
{
    ThreadLocal tl = new ThreadLocal<Integer>();    //line1
    tl.set(2021);                                   //line2
    tl.get();                                       //line3
}
//line1 creates a ThreadLocal object. t1 is a strong reference to this object;
//line2 create a new Entry after calling the set() method. It can be seen from the source code that the k in the Entry object is a weak reference to this object.

After the function01 method is executed, the strong reference tl of stack frame destruction will be eliminated. However, at this time, the key reference of an entry in the ThreadLocalMap of the thread also points to this object. If the key reference is a strong reference, the ThreadLocal object pointed to by the key and the object pointed to by v cannot be recycled by gc, resulting in memory leakage; If the key reference is a weak reference, the problem of memory leakage will probably be reduced (there is also a ray where the key is null). By using weak reference, the ThreadLocal object can be recycled smoothly after the method is executed and the key reference of the entry points to null.

After that, when we call the get,set or remove methods, we will try to delete the entry whose key is null, which can release the memory occupied by the value object.

1. Is weak quotation all right?
  1. When we assign a value to the ThreadLocal variable, it is actually the current Entry(threadLocal instance is key, value is value) stored in this threadlocalmap. The key in the Entry is a weak reference. When the external strong reference of ThreadLocal is set to null(tl=null), then according to the reachability analysis, no link of the ThreadLocal instance can reference it, and the ThreadLocal is bound to be recycled. In this way, entries with null keys will appear in threadlocalmap, and there is no way to access the values of these entries with null keys, If the current thread does not end again, the value of these entries with null key will always have a strong reference chain: thread ref - > thread - > threalocal map - > Entry - > value can never be recycled, resulting in memory leakage.
  2. Of course, if the current thread runs, threadLocal, threadlocalmap, and entry are not reachable by reference chain, they will be collected by the system during garbage collection.
  3. But in actual use, we sometimes use thread pools to maintain our threads, such as in executors When the thread is created in newfixedthreadpool(), in order to reuse the thread, the thread will not end. Therefore, threadLocal memory leakage is worthy of our attention
2. entry with null key, principle analysis

ThreadLocalMap uses the weak reference of ThreadLocal as the key. If a ThreadLocal does not have an external strong reference to reference it, the ThreadLocal will be recycled when the system is gc. In this way, entries with null keys will appear in ThreadLocalMap, and there is no way to access the values of these entries with null keys. If the current thread does not end again (for example, it happens to use the online program pool), The values of these entries with null keys will always have a strong reference chain.

Although the weak reference ensures that the ThreadLocal object pointed to by the key can be recycled in time, the value object pointed to by v needs to be recycled when the ThreadLocalMap calls get and set and finds that the key is null. Therefore, the weak reference cannot guarantee 100% that the memory is not leaked. After we do not use a ThreadLocal object, we need to manually call the remoev method to delete it, especially in the online program pool. This is not only a memory leak problem, because the threads in the thread pool are reused, which means that the ThreadLocalMap object of this thread is also reused. If we do not manually call the remove method, the subsequent threads may obtain the value value left by the previous thread, Causing bug s.

3. The set and get methods will check all Entry objects with null keys

set()


get()

remove()

conclusion

From the previous set, getentry and remove methods, we can see that in the life cycle of threadLocal, for the memory leakage problem of threadLocal, we will clean up the dirty entries with null key through three methods: explungestaleentry, cleansomeslots and replacestateentry.

Best practices

Remember to manually remove after using up

Summary:

  • ThreadLocal does not solve the problem of sharing data between threads
  • ThreadLocal is applicable to scenarios where variables are isolated between processes and shared between methods
  • ThreadLocal avoids the problem of instance thread safety by implicitly creating independent instance copies in different threads
  • Each thread holds its own unique Map and maintains the mapping between the ThreadLocal object and the specific instance. Since this Map is only accessed by the thread that holds it, there is no thread safety and lock problem
  • The Entry of ThreadLocalMap refers to ThreadLocal weakly, which avoids the problem that ThreadLocal objects cannot be recycled
  • The value of the Entry object with null key (i.e. the specific instance) and the Entry object itself will be recycled through the three methods of explungestaleentry, cleansomeslots and replacestateentry to prevent memory leakage. This is a security reinforcement method
  • Every man has his share of the world's peace

ThreadLocal and InheritableThreadLocal

Problems to be solved

  • We still introduce ThreadLocal and InheritableThreadLocal in the way of solving problems, which will make a deep impression.

At present, the web system developed by java generally has three layers: controller, service and dao. When the request arrives at the controller, the controller calls service, and the service calls dao, and then processes it.

Let's write a simple example. There are three methods to simulate controller, service and dao respectively. The code is as follows:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo1 {
    static AtomicInteger threadIndex = new AtomicInteger(1);
    //Create a thread pool to process requests
    static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
            3,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
                return thread;
            });
    //Log
    public static void log(String msg) {
        StackTraceElement stack[] = (new Throwable()).getStackTrace();
        System.out.println("****" + System.currentTimeMillis() + ",[thread :" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
    }
    //Analog controller
    public static void controller(List<String> dataList) {
        log("Accept request");
        service(dataList);
    }
    //Simulate service
    public static void service(List<String> dataList) {
        log("Execution of business");
        dao(dataList);
    }
    //Analog dao
    public static void dao(List<String> dataList) {
        log("Perform database operations");
        //Analog insert data
        for (String s : dataList) {
            log("insert data" + s + "success");
        }
    }
    public static void main(String[] args) {
        //Data to be inserted
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            dataList.add("data" + i);
        }
        //Simulate 5 requests
        int requestCount = 5;
        for (int i = 0; i < requestCount; i++) {
            disposeRequestExecutor.execute(() -> {
                controller(dataList);
            });
        }
        disposeRequestExecutor.shutdown();
    }
}
****1565338891286,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.controller(Demo1.java:36):Accept request
****1565338891286,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.controller(Demo1.java:36):Accept request
****1565338891287,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.service(Demo1.java:42):Execution of business
****1565338891287,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.service(Demo1.java:42):Execution of business
****1565338891287,[thread :disposeRequestThread-3],com.itsoku.chat24.Demo1.controller(Demo1.java:36):Accept request
****1565338891287,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:48):Perform database operations
****1565338891287,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 0 succeeded
****1565338891287,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 1 succeeded
****1565338891287,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:48):Perform database operations
****1565338891287,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data data 2 succeeded
****1565338891287,[thread :disposeRequestThread-3],com.itsoku.chat24.Demo1.service(Demo1.java:42):Execution of business
****1565338891288,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.controller(Demo1.java:36):Accept request
****1565338891287,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 0 succeeded
****1565338891288,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.service(Demo1.java:42):Execution of business
****1565338891288,[thread :disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:48):Perform database operations
****1565338891288,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:48):Perform database operations
****1565338891288,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 1 succeeded
****1565338891288,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 0 succeeded
****1565338891288,[thread :disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 0 succeeded
****1565338891288,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 1 succeeded
****1565338891288,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data data 2 succeeded
****1565338891288,[thread :disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data data 2 succeeded
****1565338891288,[thread :disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 1 succeeded
****1565338891288,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.controller(Demo1.java:36):Accept request
****1565338891288,[thread :disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data data 2 succeeded
****1565338891288,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.service(Demo1.java:42):Execution of business
****1565338891289,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:48):Perform database operations
****1565338891289,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 0 succeeded
****1565338891289,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data: data 1 succeeded
****1565338891289,[thread :disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):Insert data data 2 succeeded

When the controller, service and dao methods are called in the code, a request is simulated. In the main method, there are 5 loops to simulate the initiation of 5 requests, and then the requests are sent to the thread pool to process. In the dao, the simulation loop inserts the incoming dataList data.

Here's the problem: developers want to see where it takes more time, analyze the time consumption through logs, and track the complete log of a request. What's wrong?

The above requests are processed by thread pool. Multiple requests may be processed by one thread. It is difficult to see that those logs are the same request through the log. Can we add a unique flag to the request and output this unique flag in the log? Of course.

If our code is only as simple as the above example, I think it is still very easy. There are three methods above. Add a traceId parameter to each method, and add a traceId parameter to the log method. The code is as follows:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo2 {
    static AtomicInteger threadIndex = new AtomicInteger(1);
    //Create a thread pool to process requests
    static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
            3,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
                return thread;
            });
    //Log
    public static void log(String msg, String traceId) {
        StackTraceElement stack[] = (new Throwable()).getStackTrace();
        System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[thread :" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
    }
    //Analog controller
    public static void controller(List<String> dataList, String traceId) {
        log("Accept request", traceId);
        service(dataList, traceId);
    }
    //Simulate service
    public static void service(List<String> dataList, String traceId) {
        log("Execution of business", traceId);
        dao(dataList, traceId);
    }
    //Analog dao
    public static void dao(List<String> dataList, String traceId) {
        log("Perform database operations", traceId);
        //Analog insert data
        for (String s : dataList) {
            log("insert data" + s + "success", traceId);
        }
    }
    public static void main(String[] args) {
        //Data to be inserted
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            dataList.add("data" + i);
        }
        //Simulate 5 requests
        int requestCount = 5;
        for (int i = 0; i < requestCount; i++) {
            String traceId = String.valueOf(i);
            disposeRequestExecutor.execute(() -> {
                controller(dataList, traceId);
            });
        }
        disposeRequestExecutor.shutdown();
    }
}
****1565339559773[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.controller(Demo2.java:36):Accept request
****1565339559773[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.controller(Demo2.java:36):Accept request
****1565339559773[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo2.controller(Demo2.java:36):Accept request
****1565339559774[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.service(Demo2.java:42):Execution of business
****1565339559774[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.service(Demo2.java:42):Execution of business
****1565339559774[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:48):Perform database operations
****1565339559774[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo2.service(Demo2.java:42):Execution of business
****1565339559774[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 0 succeeded
****1565339559774[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:48):Perform database operations
****1565339559774[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 1 succeeded
****1565339559774[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:48):Perform database operations
****1565339559774[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data data 2 succeeded
****1565339559774[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 0 succeeded
****1565339559775[traceId:3],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.controller(Demo2.java:36):Accept request
****1565339559775[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 0 succeeded
****1565339559775[traceId:3],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.service(Demo2.java:42):Execution of business
****1565339559775[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 1 succeeded
****1565339559775[traceId:3],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:48):Perform database operations
****1565339559775[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 1 succeeded
****1565339559775[traceId:3],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 0 succeeded
****1565339559775[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data data 2 succeeded
****1565339559775[traceId:3],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 1 succeeded
****1565339559775[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data data 2 succeeded
****1565339559775[traceId:3],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data data 2 succeeded
****1565339559775[traceId:4],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.controller(Demo2.java:36):Accept request
****1565339559776[traceId:4],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.service(Demo2.java:42):Execution of business
****1565339559776[traceId:4],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:48):Perform database operations
****1565339559776[traceId:4],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 0 succeeded
****1565339559776[traceId:4],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data: data 1 succeeded
****1565339559776[traceId:4],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):Insert data data 2 succeeded

In the above, we solved the problem by modifying the code, but the premise is that your system is as simple as the above, with few functions and few codes to be changed. You can do so. However, contrary to our wishes, our system generally has many functions. If we all change it one by one, we will go crazy. Changing the code also involves retesting, and the risk is uncontrollable. Is there any good way?

ThreadLocal

Let's analyze the above problems. Each request is processed by a Thread. The Thread is equivalent to a person. Each request is equivalent to a task. When the task comes, the person will process it. After processing, the next request task will be processed. Do you have many pockets on your body? When you first prepare to process a task, we put the task number in the pocket of the processor, and then carry it all the way through the process. If you need to use this number during the process, you can get it directly from your pocket. Well, these problems are also taken into account when designing threads in java. There are many pockets in the Thread object to put things. There is such a variable in the Thread class:

ThreadLocal.ThreadLocalMap threadLocals = null;

How to operate these pockets in Thread? java provides us with a class ThreadLocal. The ThreadLocal object is used to operate a certain pocket in Thread. It can put things into the pocket, get the contents, and clear the contents. This pocket can only put one thing at a time, and repeated placing will cover the existing things.

Three common methods:

//Put something into a pocket in the Thread
public void set(T value);
//Get what is currently in this pocket
public T get();
//Empty the contents of this pocket
public void remove()

We use ThreadLocal to modify the above code, as follows:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo3 {
    //Create an object that stores the tracking id pocket of the requested task in the operation Thread
    static ThreadLocal<String> traceIdKD = new ThreadLocal<>();
    static AtomicInteger threadIndex = new AtomicInteger(1);
    //Create a thread pool to process requests
    static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
            3,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
                return thread;
            });
    //Log
    public static void log(String msg) {
        StackTraceElement stack[] = (new Throwable()).getStackTrace();
        //Get the contents of the current thread stored in the traceid pocket
        String traceId = traceIdKD.get();
        System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[thread :" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
    }
    //Analog controller
    public static void controller(List<String> dataList) {
        log("Accept request");
        service(dataList);
    }
    //Simulate service
    public static void service(List<String> dataList) {
        log("Execution of business");
        dao(dataList);
    }
    //Analog dao
    public static void dao(List<String> dataList) {
        log("Perform database operations");
        //Analog insert data
        for (String s : dataList) {
            log("insert data" + s + "success");
        }
    }
    public static void main(String[] args) {
        //Data to be inserted
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            dataList.add("data" + i);
        }
        //Simulate 5 requests
        int requestCount = 5;
        for (int i = 0; i < requestCount; i++) {
            String traceId = String.valueOf(i);
            disposeRequestExecutor.execute(() -> {
                //Put traceId in your pocket
                traceIdKD.set(traceId);
                try {
                    controller(dataList);
                } finally {
                    //Remove traceid from pocket
                    traceIdKD.remove();
                }
            });
        }
        disposeRequestExecutor.shutdown();
    }
}
****1565339644214[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo3.controller(Demo3.java:41):Accept request
****1565339644214[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.controller(Demo3.java:41):Accept request
****1565339644214[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.controller(Demo3.java:41):Accept request
****1565339644214[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.service(Demo3.java:47):Execution of business
****1565339644214[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo3.service(Demo3.java:47):Execution of business
****1565339644214[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:53):Perform database operations
****1565339644214[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.service(Demo3.java:47):Execution of business
****1565339644214[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 0 succeeded
****1565339644214[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:53):Perform database operations
****1565339644214[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:53):Perform database operations
****1565339644215[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 0 succeeded
****1565339644215[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 1 succeeded
****1565339644215[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 1 succeeded
****1565339644215[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 0 succeeded
****1565339644215[traceId:0],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data data 2 succeeded
****1565339644215[traceId:2],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data data 2 succeeded
****1565339644215[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 1 succeeded
****1565339644215[traceId:4],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.controller(Demo3.java:41):Accept request
****1565339644215[traceId:3],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.controller(Demo3.java:41):Accept request
****1565339644215[traceId:4],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.service(Demo3.java:47):Execution of business
****1565339644215[traceId:1],[thread :disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data data 2 succeeded
****1565339644215[traceId:4],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:53):Perform database operations
****1565339644215[traceId:3],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.service(Demo3.java:47):Execution of business
****1565339644215[traceId:4],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 0 succeeded
****1565339644215[traceId:3],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:53):Perform database operations
****1565339644215[traceId:4],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 1 succeeded
****1565339644215[traceId:3],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 0 succeeded
****1565339644215[traceId:4],[thread :disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data data 2 succeeded
****1565339644215[traceId:3],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data: data 1 succeeded
****1565339644215[traceId:3],[thread :disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):Insert data data 2 succeeded

It can be seen that the output is consistent with the result of using the traceId parameter just now, but it is much simpler. There is no need to modify the controller, service and dao codes, and the risk is also reduced.

A ThreadLocal traceIdKD is created in the code. This object is used to operate a pocket in the Thread and store the traceId with this pocket. In the main method, the traceId is put into the pocket through the traceIdKD.set(traceId) method. In the log method, the traceId in the pocket is obtained through traceIdKD.get(). After the final task is processed, the traceIdKD.remove() is called in the finally in the main; Clear the traceId in the pocket.

The official API of ThreadLocal is interpreted as:

"This class provides thread local variables. These variables are different from their common counterparts, because each thread accessing a variable (through its get or set methods) has its own local variable, which is independent of the initialization copy of the variable. ThreadLocal instances are usually private static fields in the class, and they want to associate the state with a thread (for example, user ID or transaction ID)."

InheritableThreadLocal

Continuing with the above example, the contents of the dataList are processed cyclically in dao. If the processing of the dataList is time-consuming, what can we do to speed up the processing? We have thought of using multithreading to process dataList in parallel. Let's change the code as follows:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo4 {
    //Create an object that stores the tracking id pocket of the requested task in the operation Thread
    static ThreadLocal<String> traceIdKD = new ThreadLocal<>();
    static AtomicInteger threadIndex = new AtomicInteger(1);
    //Create a thread pool to process requests
    static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
            3,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
                return thread;
            });
    //Log
    public static void log(String msg) {
        StackTraceElement stack[] = (new Throwable()).getStackTrace();
        //Get the contents of the current thread stored in the traceid pocket
        String traceId = traceIdKD.get();
        System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[thread :" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
    }
    //Analog controller
    public static void controller(List<String> dataList) {
        log("Accept request");
        service(dataList);
    }
    //Simulate service
    public static void service(List<String> dataList) {
        log("Execution of business");
        dao(dataList);
    }
    //Analog dao
    public static void dao(List<String> dataList) {
        CountDownLatch countDownLatch = new CountDownLatch(dataList.size());
        log("Perform database operations");
        String threadName = Thread.currentThread().getName();
        //Analog insert data
        for (String s : dataList) {
            new Thread(() -> {
                try {
                    //The simulated database operation takes 100 milliseconds
                    TimeUnit.MILLISECONDS.sleep(100);
                    log("insert data" + s + "success,Main thread:" + threadName);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        //Wait until the above dataList is processed
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        //Data to be inserted
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            dataList.add("data" + i);
        }
        //Simulate 5 requests
        int requestCount = 5;
        for (int i = 0; i < requestCount; i++) {
            String traceId = String.valueOf(i);
            disposeRequestExecutor.execute(() -> {
                //Put traceId in your pocket
                traceIdKD.set(traceId);
                try {
                    controller(dataList);
                } finally {
                    //Remove traceid from pocket
                    traceIdKD.remove();
                }
            });
        }
        disposeRequestExecutor.shutdown();
    }
}

Look at the output above. Some traceids are null. Why? This is because in dao, in order to improve the processing speed, a sub thread is created for parallel processing. When the sub thread calls the log, it takes something from its pocket where traceId is stored. It must be empty.

Is there any way? Can I do this?

The parent thread is equivalent to the supervisor, and the child thread is equivalent to the working younger brothers. When the supervisor asks the younger brothers to work, he copies the things in his pocket for the younger brothers to use. There may be many awesome tools in the supervisor's pocket. In order to improve the working efficiency of the younger brothers, he copies them for the younger brothers and throws them into their pockets. Then the younger brothers can take these things from their pockets for use, You can also empty your pocket.

There is an inheritableThreadLocals variable in the Thread object. The code is as follows:

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

Inheritablethreadlocales is equivalent to another pocket in the thread. What are the characteristics of this pocket? When creating a child thread, the child thread will copy all the contents of the parent thread's pocket and put them into its inheritablethreadlocales pocket. The InheritableThreadLocal object can be used to operate the inheritablethreadlocales pocket in the thread.

InheritableThreadLocal also has three common methods:

//Put something into a pocket in the Thread
public void set(T value);
//Get what is currently in this pocket
public T get();
//Empty the contents of this pocket
public void remove()

Use InheritableThreadLocal to solve the problem that the traceId cannot be output in the above sub thread. Just replace the ThreadLocal in the previous example code with InheritableThreadLocal. The code is as follows:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo4 {
    //Create an object that stores the tracking id pocket of the requested task in the operation Thread, and the child Thread can inherit the content in the parent Thread
    static InheritableThreadLocal<String> traceIdKD = new InheritableThreadLocal<>();
    static AtomicInteger threadIndex = new AtomicInteger(1);
    //Create a thread pool to process requests
    static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
            3,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
                return thread;
            });
    //Log
    public static void log(String msg) {
        StackTraceElement stack[] = (new Throwable()).getStackTrace();
        //Get the contents of the current thread stored in the traceid pocket
        String traceId = traceIdKD.get();
        System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[thread :" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
    }
    //Analog controller
    public static void controller(List<String> dataList) {
        log("Accept request");
        service(dataList);
    }
    //Simulate service
    public static void service(List<String> dataList) {
        log("Execution of business");
        dao(dataList);
    }
    //Analog dao
    public static void dao(List<String> dataList) {
        CountDownLatch countDownLatch = new CountDownLatch(dataList.size());
        log("Perform database operations");
        String threadName = Thread.currentThread().getName();
        //Analog insert data
        for (String s : dataList) {
            new Thread(() -> {
                try {
                    //The simulated database operation takes 100 milliseconds
                    TimeUnit.MILLISECONDS.sleep(100);
                    log("insert data" + s + "success,Main thread:" + threadName);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        //Wait until the above dataList is processed
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        //Data to be inserted
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            dataList.add("data" + i);
        }
        //Simulate 5 requests
        int requestCount = 5;
        for (int i = 0; i < requestCount; i++) {
            String traceId = String.valueOf(i);
            disposeRequestExecutor.execute(() -> {
                //Put traceId in your pocket
                traceIdKD.set(traceId);
                try {
                    controller(dataList);
                } finally {
                    //Remove traceid from pocket
                    traceIdKD.remove();
                }
            });
        }
        disposeRequestExecutor.shutdown();
    }
}

Tags: Java

Posted by GingerApple on Thu, 01 Sep 2022 03:37:25 +0930