X - multithreading

Posted by psy on Fri, 17 Dec 2021 16:41:52 +0100

problem

1. If there is a very time-consuming calculation in the business: + 1 million operations (the speed is slow and the execution time becomes longer)
2. Multiple execution units in the program at the same time? And they are all cycles (dead cycles)


1, Thread

1. Background

java supports multithreading (JS php) and a large number of concurrent tasks, such as 12306

Improve the performance and efficiency of programs (server programs / concurrency scenarios in games)


2. Learning objectives

  • Master the basic concept of thread and understand the thread conceptual model
  • Master the implementation of threads (Thread class / Runable interface)
  • Master thread management (start / die / pause / wait / wake up)
  • Master the synchronous operation of threads (safety): lock
  • Understand the life cycle of threads and similar concepts: the life cycle of objects / the life cycle of servlets / the life cycle of bean s in the Spring framework/
  • Master thread synchronization and mutual communication mechanism between threads (Message Middleware) producers and consumers (interview questions), when we need threads to have "order"
  • Thread pool
  • Current thread ThreadLocal

3. Thread concept

  • There are two types of multitasking:

    • Process based
    • Thread based
  • Process refers to a "self-contained" running program with its own address space; A thread is a single sequential control flow within a process

  • Process based features allow the computer to run two or more programs at the same time.

  • In the multitasking environment based on thread, thread is the smallest processing unit.

  • Multithreading purpose:

    1. Improve program execution performance

    2. Concurrent tasks

4. Thread compared to process

  • Less overhead based on threads

    • In multitasking, each process needs to allocate its own independent address space

    • Multiple threads can share the same address space and share the same process

  • Interprocess calls involve more overhead than inter thread communication

  • The switching cost between threads is lower than that between processes

2, Thread creation and startup

Create a Thread object in two ways:

  • Declare a subclass of the Thread class and override the run() method.
class MyThread extends Thread {

public void run( ) { //Override this method}

}
  • Declare a class that implements the Runnable interface and implement the run() method.
class mythread implements Runnable{

public void run( ) {/* Implement the method */ }

}

1. Inherit Thread class

/**
 * Define a custom exception type MyThread, inherit the Thread, and override run()
 */
public class MyThread extends  Thread{
    
    public MyThread(String name){
        // Call the constructor of the parent class Thread through super
        super(name);
    }

    @Override
    public void run(){

        // currentThread(): get the static method of the currently running thread object
        Thread thread = Thread.currentThread();

        while(true){
            System.out.println("Current thread name:"+thread.getName()+"\t");
           // System.out.println("ID of current thread:" + thread. Getid());

            // main


            // Add a sleep
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. Start the thread in the main method

As the main thread, main participates in the operation between multiple threads

public class ThreadDemo1 {

    public static void main(String[] args) throws InterruptedException {

        // Create an instance object of MyThread
        Thread myThread1 = new MyThread("Thread 1");
        // Wrong way
        //myThread1.run();

        // Correct way
          myThread1.start();

        // Create another instance object of MyThread, that is, add a thread
        Thread myThread2 = new MyThread("Thread 2");
        myThread2.start();
        
        // The main method is also a main thread
        //Get Thread characteristics through Thread class
       // Thread: Thread Class

        // currentThread(): get the static method of the currently running thread object
        Thread thread = Thread.currentThread();

        // Get thread characteristics:
        // getId()
        // getName()
        while(true){
            System.out.print("Current thread id:"+thread.getId()+"\t");

            // main
            System.out.println("Current thread name:"+thread.getName());

            // Add a sleep
            Thread.sleep(1000);
        }
    }
}

As the main thread, main is only responsible for startup and does not participate in the operation between multiple threads

public class ThreadDemo2 {

    public static void main(String[] args) throws InterruptedException {

        // Create an instance object of MyThread
        Thread myThread1 = new MyThread("Thread 1");
          myThread1.start();

        // Create another instance object of MyThread, that is, add a thread
        MyThread myThread2 = new MyThread("Thread 2");
        myThread2.start();
    }
}

Starting in main, the thread operation (run) and thread startup are completed by two objects respectively

/**
 *  The thread operation (run) and thread startup are completed by two objects respectively
 */
public class ThreadDemo3 {

    public static void main(String[] args) throws InterruptedException {

        // Create an instance object of MyThread
        MyThread myThread1 = new MyThread("Thread 1");
        // Thread thread1 = new Thread(myThread1);
        // thread1.start();
        // simplify:
        new Thread(myThread1).start();

        // Create another instance object of MyThread, that is, add a thread
        MyThread myThread2 = new MyThread("Thread 2");
        new Thread(myThread2).start();
    }
}

3. Implement Runnable interface

public class MyThread2 implements Runnable  {

    @Override
    public void run() {
        while(true){
            System.out.println("The name of the current thread:"+Thread.currentThread().getName());
        }
    }
}

Start in main, implement the Thread instance generated by the Runnable interface, and start the same instance through two Thread objects

/**
 *  Run the multithreaded instance, and implement the thread instance generated by the Runnable interface
 */
public class ThreadDemo4 {

    public static void main(String[] args) throws InterruptedException {

       // Create two threads and start MyThread2
        Runnable myThread2 = new MyThread2();

        //Create Thread object
        new Thread(myThread2).start();

        //Create a second Thread object
        new Thread(myThread2).start();
    }
}

3, The essence of thread: the rotation of time slice

4, Thread state

1. The condition that causes the thread to pause execution

Thread:

  • The thread has a low priority, so it cannot get CPU time.

  • Use the sleep() method to make the thread sleep.

  • Make the thread wait by calling the wait() method.

  • By calling the yield() method, the thread has explicitly transferred CPU control.

  • The thread is blocked waiting for a file I/O event.

2. Thread priority

Thread priority in Java is a constant defined in the thread class

  • NORM_PRIORITY: the value is 5

  • MAX_PRIORITY: the value is 10

  • MIN_PRIORITY: the value is 1

The default priority is NORM_PRIORITY

There are two methods for priority:

  • final void setPriority(int newp): modifies the current priority of the thread

  • final int getPriority(): returns the priority of the thread

3. Thread synchronization

  • Sometimes two or more threads may try to access a resource at the same time

    For example, one thread might try to read data from a file, while another thread might try to modify data in the same file

  • In this case, the data may become inconsistent

  • To ensure that a shared resource is used by only one thread at any point in time, synchronization is used

Use the synchronized keyword to identify

code:

	synchronized (tickets){
                    
      // Selling tickets
      System.out.println(Thread.currentThread().getName()+"Sell No"+tickets+"Ticket");
      tickets--;
}

(1) Synchronization block

public class Ticket implements Runnable {

    // Initial status: 100 tickets - ticket warehouse
    private Integer tickets = 100;
    
  
    @Override
    public void run(){

            while(tickets>0){
                
                synchronized (tickets){
                    
                        // Selling tickets
                        System.out.println(Thread.currentThread().getName()+"Sell No"+tickets+"Ticket");
                        tickets--;
                    }
                  
                // Slowing down through sleep,
                // Benefits: other threads are easier to obtain CPU resource time slices, and the rotation is more balanced
               /* try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            
        }

    }
}

(2) Why oversold

(3) Deadlock

Deadlock occurs when two threads depend on a pair of synchronized objects. For example:

One thread enters the monitor on object A and another thread enters the monitor on object B. If the thread in A tries to call any synchronized method on B, A deadlock will occur.

Class A:

public class A implements Runnable {

    @Override
    public void run() {
        synchronized (Resource.i){
            System.out.println("A Locked  Resource.i");

           /* try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/

            // Request locking j
            synchronized (Resource.j){
                System.out.println("A Locked  Resource.j");
            }
        }
    }
}

Class B:

public class B implements Runnable {

    @Override
    public void run() {
        synchronized (Resource.j){
            System.out.println("B Locked  Resource.j");

            /*try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/

            // Request lock i
            synchronized (Resource.i){
                System.out.println("B Locked  Resource.i");
            }
        }
    }
}

Synchronize locks to use:

public class Resource {

    public static String i = "1";
    public static String j = "2";

    // If there are two threads, each holds a resource first,
    // At the same time, request the other party's resources in the synchronization operation
}

Start the thread and create a deadlock:

public class DeadlockDemo {
    public static void main(String[] args) {
        // Declare that two threads use each other's locked resources
        new Thread(new A()).start();
        new Thread(new B()).start();
    }
}

Deadlocks rarely occur, but once they occur, they are difficult to debug.

How to avoid: avoid nesting synchronization operations in synchronization operations

5, Communication between threads

1. Wait notify mechanism (wait and wake up)

  1. To avoid rotation detection, Java provides a well-designed inter thread communication mechanism, using wait(), notify() and notifyAll() methods.
    These methods are implemented as final methods in the Object class.
    These three methods can only be called in the synchronized method.

  2. The wait() method tells the called thread to exit the monitor and enter the wait state until other threads enter the same monitor and call the notify() method.

  3. The notify() method notifies the first thread calling wait() on the same object.

  4. The notifyAll() method notifies all threads calling wait() that the thread with the highest priority will run first.

Application: producer and consumer model

Business scenarios where the program needs high-speed / efficient processing: order processing in the mall, inventory processing,

  • producer
public class Prductor implements Runnable{

    private List<Integer> youtong;

    public Prductor(List<Integer> youtong){
        this.youtong = youtong;
    }

   // private int i = 0;

    @Override
    public void run() {
        while(true){
          //  The resources of multiple threads with wait and notify operations must be synchronized
            synchronized (youtong){
                // When there are more than 5 letters in the mailbox
                if(youtong.size() == 5){
                   // i = 0;
                    //Stop writing, wait()
                    try {
                        youtong.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    // Increase the number of letters delivered
                    youtong.add(1);
                   // i++;
                    System.out.println("Wrote the second"+youtong.size()+"Letter");
                    //Notify the postman to start sending notify/ notifyAll in time
                    // Wake up other wait ing threads
                    youtong.notifyAll();
                }
            }
        }

    }
}
  • consumer
public class Consumer implements Runnable{

    private List<Integer> youtong;

    public Consumer(List<Integer> youtong){
        this.youtong = youtong;
    }

    // Mark of consumption quantity
    //private int i = 0;

    @Override
    public void run() {
        while(true){
          //  The resources of multiple threads with wait and notify operations must be synchronized
            synchronized (youtong){
                // When the mail in the mailbox is less than 1
                if(youtong.size() == 0){
                    //i = 0;
                    //, stop sending the message, wait()
                    try {
                        youtong.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    System.out.println("Sent out the second"+youtong.size()+"Letter");
                    // Another letter was sent and the first one was taken out of the letter queue
                    youtong.remove(0);
                    //i++;

                    //Notify the postman to start sending notify/ notifyAll in time
                    // Wake up other wait ing threads
                    youtong.notifyAll();
                }
            }
        }

    }
}

Start:

public class Demo {
    public static void main(String[] args) {
        List<Integer> youtong = new ArrayList<>();


        // The producer began to write letters
        new Thread(new Prductor(youtong)).start();
        new Thread(new Prductor(youtong)).start();
        new Thread(new Prductor(youtong)).start();

        // Consumers start sending letters
        new Thread(new Consumer(youtong)).start();
        new Thread(new Consumer(youtong)).start();
        new Thread(new Consumer(youtong)).start();
    }
}

2. Thread status

Under certain conditions, the state of a thread will change. Threads have the following states:

(1) New status (New)

A new thread object is created.

(2) Ready status (Runnable)

After the thread object is created, other threads call the start() method of the object. Threads in this state are located in the "runnable thread pool", become runnable and only wait for acquisition CPU Right to use. That is, the process in the ready state has all the resources required for operation except the CPU.

(3) Running status

The ready thread gets the CPU and executes the program code.

(4) Blocked status

A blocking state is when a thread gives up CPU usage for some reason and temporarily stops running. Until the thread enters the ready state, it has no chance to go to the running state.

There are three types of congestion

  1. Wait blocking: the running thread executes the wait() method, which will release all the resources occupied, and the JVM will put the thread into the "wait pool". After entering this state, you cannot wake up automatically. You must rely on other threads to call notify() or notifyAll() methods to wake up,

  2. Synchronization blocking: when a running thread obtains the synchronization lock of an object, if the synchronization lock is occupied by another thread, the JVM will put the thread into the * * lock pool * *.

  3. Other blocking: when the running thread executes the sleep() or join() method, or makes an I/O request, the JVM will set the thread to blocking state. When the sleep() state times out, the join() waits for the thread to terminate or time out, or the I/O processing is completed, the thread will return to the ready state.

(5) Dead state

When the thread finishes executing or exits the run() method due to an exception, the thread ends its life cycle.


3. Process

  1. There are two ways to implement threads: one is to inherit the Thread class, and the other is to implement the Runnable interface. However, after we new this object, the Thread enters the initial state;

  2. When the object calls the start() method, it enters the ready state;

  3. After entering ready, when the object is operating system If selected, the CPU time slice will enter the running state;

  4. After entering the running state:

    (1). After the run () method or main() method ends, the thread enters the termination state;

    (2). When a thread calls its own sleep() method or the join() method of another thread, the process gives up the CPU and then enters a blocking state (this state stops the current thread but does not release the resources it occupies, that is, after calling the sleep() function, the thread will not release its "lock flag"). When sleep() ends or join() ends, the thread enters the runnable state and continues to wait for the OS to allocate CPU time slices. Typically, sleep() is used to wait for a resource to be ready: test After finding that the conditions are not met, let the thread block for a period of time and retest until the conditions are met.

    (3). The thread calls the * * yield() * * method, which means to give up the currently obtained CPU time slice and return to the ready state. At this time, it is in the same competitive state with other processes, and the OS may then let this process enter the running state; The effect of calling yield () is equivalent to the scheduler thinking that the thread has executed enough time slices to need to go to another thread. Yield () only returns the current thread to the executable state, so the thread executing yield () may be executed immediately after entering the executable state.

    (4). When the thread has just entered the runnable state (note that it has not yet run), it finds that the resource to be called is synchronized. If the lock tag cannot be obtained, it will immediately enter the lock pool state and wait for the lock tag to be obtained (there may already be other threads in the lock pool waiting to obtain the lock mark, and they are in the queue state, i.e. first come, first served). Once the thread obtains the lock mark, it will turn to the ready state and wait for the OS to allocate the CPU time slice;

    (5). suspend() and resume() methods: the two methods are used together. suspend() makes the thread enter the blocking state and will not recover automatically. Its corresponding resume() must be called to make the thread enter the executable state again** Typically, suspend() and resume() are used to wait for the result produced by another thread: * * test results have not been generated, let the thread block, and the other thread produces the result, then call resume() to restore it.

    (6). wait() and notify() methods: when a thread calls the wait() method, it will enter the waiting queue (entering this state will release all resources it occupies, which is different from the blocking state). After entering this state, it cannot wake up automatically. It must rely on other threads to call the notify() or notifyAll() method to wake up (because notify() only wakes up one thread, However, we can't determine which thread to wake up. Maybe the thread we need to wake up can't be awakened. Therefore, in actual use, we generally use the notifyAll() method to wake up some threads). After the thread is awakened, it will enter the lock pool and wait for the lock mark to be obtained.

wait() causes the thread to enter the blocking state, which has two forms

  • A method that allows specifying a period of time in milliseconds as a parameter. When the corresponding notify() is called or exceeds the specified time, the thread re enters the executable state, that is, the ready state
  • The other has no parameters, and the corresponding notify() must be called
  • When wait() is called, the thread will release the "lock flag" it holds, so that other synchronized data in the object where the thread is located can be used by other threads.
  • waite() and notify() must be called in the synchronized function or synchronized block because they operate on the "lock flag" of the object.
  • If it is called in the non synchronized function or non synchronized block, although it can be compiled, an exception of IllegalMonitorStateException will occur at runtime.
  • Note the difference: at first glance, the wait() and notify() methods are no different from the suspend() and resume() method pairs, but in fact they are very different. The core difference is that suspend() and all other methods described above will not release the occupied lock (if occupied) when the thread is blocked, while wait() and notify() are opposite.

The above core differences lead to a series of differences in detail

First of all, all the methods described above belong to the Thread class, but the pair of wait() and notify() methods directly belong to the Object class, that is, all objects have this pair of methods. At first glance, this seems incredible, but in fact it is very natural, because when this pair of methods are blocked, the occupied lock will be released, and the lock is owned by any Object. Calling the wait() method of any Object will lead to Thread blocking, and the lock on the Object will be released. Calling the notify() method of any Object causes a randomly selected Thread blocked by calling the wait() method of the Object to unblock (but it can not be executed until the lock is obtained).

Secondly, all the methods described before can be called at any location. * * but the wait() and notify() methods must be called in synchronized methods or blocks. * * reason is also very simple. Only when the thread in the synchronized method or block occupies the lock, can the lock be released. Similarly, the lock on the object calling this pair of methods must be owned by the current thread so that a lock can be released. Therefore, the pair of method calls must be placed in such a synchronized method or block, and the locked object of the method or block is the object calling the pair of methods. If this condition is not met, the program can still be compiled, but an IllegalMonitorStateException will appear at run time.

The above characteristics of wait() and notify() methods determine that they are often used with synchronized methods or blocks. If you compare them with the interprocess communication machine of the operating system, you will find their similarities: synchronized methods or blocks provide functions similar to the operating system primitives, and their execution will not be disturbed by the multithreading mechanism, This opposite law is equivalent to the block and wake up primitives (both methods are declared synchronized). Their combination enables us to realize a series of exquisite inter process communication on the operating system algorithm (such as semaphore algorithm) and used to solve various complex inter thread communication problems.

There are two more points about the wait() and notify() methods:

  • First: the threads that are unblocked by calling the notify() method are randomly selected from the threads blocked by calling the wait() method of the object. We can't predict which thread will be selected, so we should be careful when programming to avoid problems caused by this uncertainty.

  • Second: in addition to notify(), there is another method notifyAll(). The only difference is that calling notifyAll() will unblock all threads blocked by calling the wait() method of the object at one time. Of course, only the thread that obtains the lock can enter the executable state.

When it comes to blocking, we have to talk about deadlock. After a brief analysis, we can find that the calls of suspend() method and wait() method without specifying timeout period may cause deadlock. Unfortunately, Java Deadlock avoidance is not supported at the language level. We must be careful to avoid deadlock in programming.

6, lambda expression

1. Essence:

Application of functional programming (taking function as parameter)

2. Use

(1) Create an instance of the interface

  1. Define an interface first:
public interface I {
    void f();
}
  1. Create an instance of the interface:
 I i = ()->{
            System.out.println("i2");
        };

Case of overriding deadlock with lambda:

public static void main(String[] args) {
        // Declare that two threads use each other's locked resources
        new Thread(()->{
            synchronized (Resource.i){
                System.out.println("The request has arrived i");
                synchronized (Resource.j){
                    System.out.println("The request has arrived j");
                }
            }
        }).start();

        new Thread(()->{
            synchronized (Resource.j){
                System.out.println("The request has arrived j");
                synchronized (Resource.i){
                    System.out.println("The request has arrived i");
                }
            }
        }).start();
    }

3. Use function call as parameter:

Use the forEach method of List

public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        strings.add("a");
        strings.add("b");
        strings.add("c");

  		//Regular for loop
        for (String s: strings ) {
            System.out.println(s);
        }

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

        // Use lambda expression Caller:: method name
        strings.forEach(System.out::println);
        
    }

Replace with a custom show method

public class ForDemo {

    public void show(String string){
        System.out.println(string+string);
    }

    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        strings.add("a");
        strings.add("b");
        strings.add("c");

        for (String s: strings ) {
            System.out.println(s);
        }

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

        // Use lambda expression Caller:: method name
       // strings.forEach(System.out::println);
        // Change to show
        ForDemo forDemo = new ForDemo();
        strings.forEach(forDemo::show);
    }
}

If the show method is static:

public class ForDemo {

    public static void show(String string){
        System.out.println(string+string);
    }

    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        strings.add("a");
        strings.add("b");
        strings.add("c");

        for (String s: strings ) {
            System.out.println(s);
        }

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

        // Use lambda expression Caller:: method name
        strings.forEach(System.out::println);
        
    }
}

If the method has parameters

public interface I {
    void f(int i);
}

When calling, () must contain the parameter name (arbitrary)

 public static void main(String[] args) {

        // To execute the f method in the I interface
        // Without implementing the class
        //2. Use lambda expressions
        
				// a represents the parameter i in the method
        I i = (a)->{
            System.out.println("a:"+a);
            System.out.println("i2");
        };

        // Call f method
        i.f();
    }

Advantage: simplified code

7, ThreadPool

1. Background:

  • When there are a large number of Thread objects, it is not suitable to create them repeatedly. Virtual machine brings a great burden on managing thread objects
  • The utilization of Thread objects should also be enhanced

Based on the above reasons, the concept of thread pool is proposed:

  • Principle: through the management of thread pool, thread objects are taken out of the queue when they are used, and then returned to the queue after use, which increases the utilization of Thread objects and facilitates management

2. Implementation method

public class ThreadPoolDemo {

    public static void main(String[] args) {

        // Create an instance of the blocking queue
        BlockingQueue blockingQueue = new SynchronousQueue();

        // Instantiate with ThreadPoolExecutor

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
                10,10L, TimeUnit.MINUTES,blockingQueue);

        // Execution through thread pool
        threadPoolExecutor.execute(new MyThread());
    }
}

8, Thread current scope (ThreadLocal)

1. Background:

Data transmission and data synchronization are often involved in applications

2. Data transmission:

  1. Explicit value passing: (constructor / setter)
  2. Implicit value passing: ThreadLocal

3. Data synchronization:

  1. Lock, by means of synchronization
  2. Through ThreadLocal

4. ThreadLocal operation

Use ThreadLocal only:

public class ThreadLocalDemo {

    public static void main(String[] args) {

        // Create an instance of QQUser on the server (registration / login)
        ThreadLocal threadLocal = new ThreadLocal();
        QQUser user10000 = new QQUser();
        user10000.setId("10000");
        user10000.setName("Father Ma");
        user10000.setPwd("10000");

        threadLocal.set(user10000);

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

        //It is generally placed at the end of thread operation to clear the data
       // threadLocal.remove();

        // It is responsible for the operation of transmitting information to users in the server
        ThreadLocal threadLocal2 = new ThreadLocal();
        System.out.println(threadLocal2.get());
    }
}

Operation results:

You will find that the expected purpose is not achieved: the data is not obtained in the same thread

  • The reason is that ThreadLocal maintains a Map through the current Thread of the Thread and uses this as the key, while multiple ThreadLocal objects lead to different key values

  • Improvement: wrapped as UserThreadLocal

public class UserThreadLocal {

    // ThreadLocal should ensure that the key is unique. It is usually declared as static
    private static ThreadLocal threadLocal = new ThreadLocal();

    public Object get(){
        return threadLocal.get();
    }

    public void set(Object o){
        threadLocal.set(o);
    }

    public void remove(){
        threadLocal.remove();
    }
}

Instantiate UserThreadLocal instead

public class ThreadLocalDemo2 {

    public static void main(String[] args) {

        // Create an instance of QQUser on the server (registration / login)
        UserThreadLocal userThreadLocal1 = new UserThreadLocal();
        QQUser user10000 = new QQUser();
        user10000.setId("10000");
        user10000.setName("Father Ma");
        user10000.setPwd("10000");

        userThreadLocal1.set(user10000);
        System.out.println(userThreadLocal1.get());

        //It is generally placed at the end of thread operation to clear the data
       // threadLocal.remove();

        // It is responsible for the operation of transmitting information to users in the server
        UserThreadLocal userThreadLocal2 = new UserThreadLocal();
        System.out.println(userThreadLocal2.get());
    }
}

result:

Meet expectations

So, what if it operates in two different threads?

public class ThreadLocalDemo3 {

    public static void main(String[] args) {

        //Build the first thread
        new Thread(()->{
            // Create an instance of QQUser on the server (registration / login)
            UserThreadLocal userThreadLocal1 = new UserThreadLocal();
            QQUser user10000 = new QQUser();
            user10000.setId("10000");
            user10000.setName("Father Ma");
            user10000.setPwd("10000");

            userThreadLocal1.set(user10000);
            System.out.println("userThreadLocal1:"+userThreadLocal1.get());
        }).start();

        new Thread(()->{
            // It is responsible for the operation of transmitting information to users in the server
            UserThreadLocal userThreadLocal2 = new UserThreadLocal();
            System.out.println("userThreadLocal2:"+userThreadLocal2.get());
        }).start();
    }
}

result

Different threads cannot be shared, which reflects the characteristics of their scope

Topics: Java Spring Interview JavaSE