Multithreaded learning one

Multithreaded learning one

1 Overview

In general, we don't need to contact concurrency, but we can't just be a CRUD master. We still need to strengthen our skills. The better the technology, the smoother the road behind. New generation farmers and famous workers, let's study together! This content comes from the dark horse programmer of station B. For learning records only

2.1 process and process

process

  • The program consists of instructions and data, but to run these instructions and read and write the data, you must load the instructions into the cpu and the data into the memory. Disk, network and other devices are also required during the operation of instructions. The process is used to load instructions, manage memory and manage IO
  • When an instruction is run to load the program code from disk to memory, a process is started
  • A process can be regarded as an instance of a program. Most programs can run multiple instance processes (such as Notepad, browser, etc.), and some can only run one instance process (such as 360 security guard)

thread

  • A process can be divided into one or more threads.
  • A thread is an instruction stream, which gives instructions in the instruction stream to the CPU for execution in a certain order
  • In Java, thread is the smallest scheduling unit and process is the smallest unit of resource allocation. In windows, the process is inactive, just for
    It is a container for threads (it feels more like learning the principle of computer composition here!)

distinguish between

Processes are basically independent of each other, and threads exist in processes and are a subset of processes
A process has shared resources, such as memory space, for its internal threads to share
Inter process communication is complex
The process communication of the same computer is called IPC (inter process communication)
Process communication between different computers needs to pass through the network and abide by common protocols, such as HTTP
Thread communication is relatively simple because they share memory within the process. An example is that multiple threads can access the same shared variable
Threads are lighter, and the cost of thread context switching is generally lower than that of process context switching

2.2 parallelism and concurrency

Concurrent

In a single core cpu, threads are actually executed serially. There is a component in the operating system called task scheduler, which integrates the time slice of cpu (windows)
The next time slice (about 15ms at least) is distributed to different programs, only because the cpu switches between processes very quickly (the time slice is very short), which is very human
Perception runs at the same time. Generally, this method of using CPU in turn by threads is called concurrency

parallel

Under multi-core cpu, each core can schedule running threads. At this time, threads can be parallel, and different threads use different CPUs to execute at the same time.

distinguish between

Quote Rob Pike's Description: concurrent is the ability to deal with multiple things at the same time, and parallel is the ability to do multiple things at the same time

  • When a housewife cooks, cleans and feeds her children, she takes turns to do these many things, which is concurrent
  • Three nannies were hired, one for cooking, one for cleaning and one for nursing. They did not interfere with each other. At this time, they were in parallel
  • The housewife hired a nanny, and they did these things together. At this time, there was both concurrence and concurrence (at this time, there would be competition, for example, there was only one pot and one pot)
    When one person uses the pot, the other person has to wait)

application

Synchronous and asynchronous concepts

From the perspective of the caller, if you need to wait for the result to return to continue running, it is synchronous. If you don't need to wait, it is asynchronous

1) Design

Multithreading can make the execution of methods asynchronous. For example, when reading disk files, it is assumed that the reading operation takes 5 seconds. If there is no thread scheduling mechanism, the cpu can only wait 5 seconds and can't do anything.

2) Conclusion

  • For example, in a project, video file format conversion is time-consuming. At this time, open a new thread to process video conversion to avoid blocking the main thread
  • The asynchronous servlet of tomcat has a similar purpose. It allows the user thread to handle long-time operations and avoid blocking the working thread of tomcat
  • In the ui program, open the thread for other operations to avoid blocking the ui thread

java thread

3.1 creating and running threads

Method 1: directly use Thread

// The parameter of the constructor is to assign a name to the thread. It is recommended to give a name to the thread
Thread t1 = new Thread("t1") {
 @Override
 // The task to be executed is implemented within the run method
 public void run() {
 log.debug("hello");
 }
};
t1.start();

Method 2: use Runnable to cooperate with Thread

Separate [thread] and [task] (code to be executed). Thread represents thread and Runnable runnable task (code to be executed by thread) Test2.java

// Create task object
Runnable task2 = new Runnable() {
 @Override
 public void run() {
 log.debug("hello");
 }
};
// Parameter 1 is the task object; Parameter 2 is the thread name. It is recommended to give the thread a name
Thread t2 = new Thread(task2, "t2");
t2.start();

Summary

Method 1 combines threads and tasks. Method 2 separates threads and tasks. It is easier to use runnable to cooperate with advanced API s such as Thread pool. Runnable makes the task class separate from the Thread inheritance system and more flexible. By viewing the source code, you can find that method 2 is actually executed through method 1!

Method 3: FutureTask with Thread

FutureTask can receive Callable parameters to handle Test3.java with returned results

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // The third way to implement multithreading can return data
        FutureTask futureTask = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("Multithreaded task");
                Thread.sleep(100);
                return 100;
            }
        });
        // The main thread is blocked and synchronously waits for the result of task execution
        new Thread(futureTask,"My name").start();
        log.debug("Main thread");
        log.debug("{}",futureTask.get());
    }

Future is to cancel the execution result of a specific Runnable or Callable task, query whether it is completed, and obtain the result. If necessary, you can get the execution result through the get method, which will block until the task returns the result.

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Future provides three functions:

  1. Judge whether the task is completed;

  2. Ability to interrupt tasks;

  3. Be able to obtain task execution results.

FutureTask is an implementation of Future and Runable

3.2 thread operation principle

Virtual machine stack and stack frame

Pseudo machine stack describes the memory model of Java method execution: when each method is executed, a stack frame will be created at the same time to store local variable table, operand stack, dynamic link, method exit and other information. It is private to the thread. When multithreading is used in Java, each thread will maintain its own stack frame! Each thread can only have one active stack frame, corresponding to the method currently executing

Thread Context Switch

For the following reasons, the cpu no longer executes the current thread, but executes the code of another thread

  • The cpu time slice of the thread runs out (each thread executes in turn. See the previous concept of parallelism)
  • garbage collection
  • Threads with higher priority need to run
  • The thread calls sleep, yield, wait, join, park, synchronized, lock and other methods

When a Context Switch occurs, the operating system needs to save the state of the current thread and restore the state of another thread, which is the corresponding concept in Java
It is the Program Counter Register. Its function is to remember the execution address of the next jvm instruction. It is thread private

3.3 common methods of thread

3.3.1 start and run

Call start

    public static void main(String[] args) {
        Thread thread = new Thread(){
          @Override
          public void run(){
              log.debug("I am a new thread running");
              FileReader.read(fileName);
          }
        };
        thread.setName("New thread");
        thread.start();
        log.debug("Main thread");
    }

Output: the program runs on the t1 thread, and the call of the contents in the run() method is asynchronous Test4.java

11:59:40.711 [main] DEBUG com.concurrent.test.Test4 - Main thread
11:59:40.711 [New thread] DEBUG com.concurrent.test.Test4 - I am a new thread running
11:59:40.732 [New thread] DEBUG com.concurrent.test.FileReader - read [test] start ...
11:59:40.735 [New thread] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 3 ms

Call run

thread.start() of the above code; Change to thread.run(); The output results are as follows: the program is still running in the main thread, and the call of the contents in the run () method is still synchronous

12:03:46.711 [main] DEBUG com.concurrent.test.Test4 - I am a new thread running
12:03:46.727 [main] DEBUG com.concurrent.test.FileReader - read [test] start ...
12:03:46.729 [main] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 2 ms
12:03:46.730 [main] DEBUG com.concurrent.test.Test4 - Main thread

Summary

The direct call to run() is to execute run() in the main thread without starting a new thread
Using start() starts a new thread and indirectly executes the code in the run() method through the new thread

3.3.2 sleep and yield

sleep

  1. Calling sleep will make the current thread enter the Timed Waiting state (blocking) from Running
  2. Other threads can use the interrupt method to interrupt the sleeping thread, and the interrupted thread will throw an InterruptedException exception [Note: the interrupted thread is the sleeping thread, not the threads in other states]
  3. After sleep, the thread may not be executed immediately (it needs to be allocated to cpu time slice)
  4. It is recommended to replace Thread's sleep() with TimeUnit's sleep() for better readability

yield

  1. Calling yield will enable the current thread to enter the Runnable ready state from Running, and then schedule other threads.
  2. The specific implementation depends on the task scheduler of the operating system (that is, no other thread may be executing. Although the yield method is called, it is useless)

Summary

yield causes the cpu to call other threads, but the cpu may reallocate time slices to this thread; Sleep needs to wait for sleep time before it can be allocated cpu time slice

3.3.3 thread priority

Thread priority will prompt the scheduler to schedule the thread first, but it is only a prompt, and the scheduler can ignore it
If the cpu is busy, the thread with higher priority will get more time slices, but when the cpu is idle, priority has little effect

3.3.4 join

When t1.join is invoked in the main thread, the main thread will wait for the execution of the T1 thread before continuing Test10.java.

    private static void test1() throws InterruptedException {
        log.debug("start");
        Thread t1 = new Thread(() -> {
            log.debug("start");
            sleep(1);
            log.debug("end");
            r = 10;
        },"t1");
        t1.start();
        t1.join();
        log.debug("The result is:{}", r);
        log.debug("end");
    }

3.3.5 detailed explanation of interrupt method

Interrupt the thread of sleep, wait and join

First, learn about the interrupt() method: Blog address

Sleep, wait and join threads. These methods will make the thread enter the blocking state. Take sleep as an example, Test7.java

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                log.debug("Thread task execution");
                try {
                    Thread.sleep(10000); // wait, join
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    log.debug("Interrupted");
                }
            }
        };
        t1.start();
        Thread.sleep(500);
        log.debug("111 Is it interrupted?{}",t1.isInterrupted());
        t1.interrupt();
        log.debug("222 Is it interrupted?{}",t1.isInterrupted());
        Thread.sleep(500);
        log.debug("222 Is it interrupted?{}",t1.isInterrupted());
        log.debug("Main thread");
    }

Output result: (I will mix the word interrupt and interrupt below) you can see that the thread that interrupts sleep will clear the interrupt state. Just after being interrupted, the value of t1.isInterrupted() is true, and then it becomes false, that is, the interrupt state will be cleared. Then whether the thread has been interrupted can be judged by exceptions. [note that if the thread is interrupted by join(), wait() blocked, it will also be cleared. Interrupt status will be cleared means that the interrupt status is set to false, and interrupt status will be set means that the interrupt status is set to true]

17:06:11.890 [Thread-0] DEBUG com.concurrent.test.Test7 - Thread task execution
17:06:12.387 [main] DEBUG com.concurrent.test.Test7 - 111 Is it interrupted? false
17:06:12.390 [Thread-0] DEBUG com.concurrent.test.Test7 - Interrupted
17:06:12.390 [main] DEBUG com.concurrent.test.Test7 - 222 Is it interrupted? true
17:06:12.890 [main] DEBUG com.concurrent.test.Test7 - 222 Is it interrupted? false
17:06:12.890 [main] DEBUG com.concurrent.test.Test7 - Main thread

Interrupt a running thread

Interrupt the normal running thread. The thread will not pause, but call the method Thread.currentThread().isInterrupted(); If the return value of is true, you can judge Thread.currentThread().isInterrupted(); To manually stop the thread

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(true) {
                boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted) {
                    log.debug("Interrupted, Exit loop");
                    break;
                }
            }
        }, "t1");
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
    }

Two stage termination mode of termination mode

Two Phase Termination is to consider how to gracefully terminate another thread T2 in one thread T1? Elegance here refers to giving T2 a chance to take care of the afterlife (such as releasing the lock).

As shown below: the isInterrupted() method of the thread can obtain the interrupt flag of the thread. If the thread is interrupted during sleep, the interrupt flag will not change to false, but an exception will be thrown if it is interrupted during sleep. We manually set the interrupt flag to true accordingly; If it is interrupted during the normal operation of the program, the interrupt flag is automatically set to true. If we handle these two situations well, we can take care of the aftermath with confidence!

The code implementation is as follows:

@Slf4j
public class Test11 {
    public static void main(String[] args) throws InterruptedException {
        TwoParseTermination twoParseTermination = new TwoParseTermination();
        twoParseTermination.start();
        Thread.sleep(3000);  // Let the monitoring thread execute for a while
        twoParseTermination.stop(); // Stop monitoring thread
    }
}


@Slf4j
class TwoParseTermination{
    Thread thread ;
    public void start(){
        thread = new Thread(()->{
            while(true){
                if (Thread.currentThread().isInterrupted()){
                    log.debug("The thread ended.. I'm taking care of my affairs");
                    break;
                }
                try {
                    Thread.sleep(500);
                    log.debug("Performing monitoring functions");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }
    public void stop(){
        thread.interrupt();
    }
}

3.3.6 comparison of sleep, yiled, wait and join

About the principle of join and the comparison of these methods: Look here

Supplement:

  1. sleep, join, yield, interrupted are the methods in the Thread class
  2. wait/notify is a method in object

sleep does not release the lock and cpu
join release lock and preempt cpu
yiled does not release the lock and release the cpu
wait to release the lock and cpu

3.4 daemon thread

By default, the java process will not stop until all threads are finished. However, there is a special thread called daemon thread. When all other threads are finished, the java process will stop even if the daemon thread is not finished and the code is not executed. Ordinary thread T1 can call t1.setDeamon(true); Method becomes a daemon thread

be careful
The garbage collector thread is a daemon thread
Acceptor and Poller threads in Tomcat are daemon threads, so Tomcat will not wait after receiving the shutdown command
Wait until they finish processing the current request

3.5 five thread states

The five states are divided mainly from the level of operating system

  1. In the initial state, only thread objects are created at the language level, that is, Thead thread = new Thead();, Not yet associated with an operating system thread
  2. Runnable state, also known as ready state, means that the thread has been created, associated with the operating system, and can run after the cpu allocates time slices to it
  3. Running status refers to that the thread has obtained the CPU time slice and is running
    1. When the CPU time slice runs out, the thread will switch to the runnable state and wait for the CPU to allocate the time slice again, which will lead to the context switching mentioned earlier
  4. Blocking state
    1. If a blocking API, such as BIO read / write file, is called, the thread will not actually use the CPU and will not allocate CPU time slices, which will lead to context switching and enter the [blocking state]
    2. After the BIO operation is completed, the operating system will wake up the blocked thread and switch to [runnable state]
    3. The difference from [runnable state] is that as long as the operating system does not wake up the threads, the scheduler will not consider scheduling them, and the CPU will not allocate time slices
  5. The termination status indicates that the thread has completed execution, the life cycle has ended, and will not be converted to other states

3.6 six thread states

This is described from the Java API level, which is what we mainly study. Detailed diagram of state transition: address
According to the Thread.State enumeration, Test12.java is divided into six states

  1. NEW is the same as the initial state of the five states
  2. RUNNABLE is the state after the start() method is called. Note that the RUNNABLE state at the Java API level covers the RUNNABLE state, running state and io blocking state at the operating system level (the thread blocking caused by BIO is indistinguishable in Java and is still considered RUNNABLE)
  3. BLOCKED , WAITING , TIMED_WAITING is the breakdown of [blocking state] in the Java API layer, which will be described in the section of state transition later
    Elaborate

Thread problem

4.1 root cause analysis of thread problems

The root cause of the thread problem is the thread context switching, which causes the instructions in the thread to switch to other threads before they are executed. Here is an example Test13.java

    static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 1;i<5000;i++){
                count++;
            }
        });
        Thread t2 =new Thread(()->{
            for (int i = 1;i<5000;i++){
                count--;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("count The value of is{}",count);
    }

I will analyze it from the level of bytecode:

getstatic i // Gets the value of the static variable i
iconst_1 // Prepare constant 1
iadd // Self increasing
putstatic i // Store the modified value into the static variable i
    
getstatic i // Gets the value of the static variable i
iconst_1 // Prepare constant 1
isub // Self subtraction
putstatic i // Store the modified value into the static variable i

You can see that count + + and count -- operations actually need these four instructions to complete, so the problem here comes! The memory model of Java is as follows. To complete the self increase and self decrease of static variables, data exchange needs to be carried out in main memory and working memory:

If the code runs in normal order, the value of count will not be calculated wrong

When a negative number occurs:

When a positive number occurs:

Further description of the problem

Critical zone

  1. Multithreading itself is no problem for a program to run

  2. The problem occurs when multiple threads share resources

    1. There is no problem for multiple threads to read shared resources at the same time
    2. The problem occurs when reading and writing to shared resources at the same time
  3. First define a concept called critical area: if there are multithreaded read-write operations on shared resources in a piece of code, it is called critical area

    1. as

      static int counter = 0;
      static void increment()
      {// Critical zone
       counter++;
      }
      static void decrement()
      {// Critical zone
       counter--;
      }
      

Race condition

If multiple threads execute in the critical area, the result problem caused by the uncertain execution of code instructions is called race condition

4.2 synchronized solutions

In order to avoid the occurrence of race conditions in the critical region, it can be achieved by many means

  • Blocking solution: synchronized, Lock
  • Non blocking solution: atomic variables

Now let's talk about using synchronized to solve this problem, which is commonly known as object lock. It uses mutual exclusion so that at most one thread holds the object lock at the same time, and other threads will block if they want to obtain the lock. In this way, it can ensure that the thread with the lock can safely execute the code in the critical area without worrying about thread context switching

be careful
Although the synchronized keyword can be used to complete mutual exclusion and synchronization in java, they are different: mutual exclusion ensures the occurrence of race conditions in the critical area. Only one thread can execute the code synchronization in the critical area at the same time. The code synchronization in the critical area is due to the different execution order of threads, but one thread needs to wait for other threads to run to a certain point.

synchronized

synchronized(object) // If thread 1 obtains the lock, the status of thread 2 is blocked
{
 Critical zone
}

The above example program uses synchronized as follows, and the calculated result is correct! Test13.java

static int counter = 0;
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException {
     Thread t1 = new Thread(() -> {
         for (int i = 0; i < 5000; i++) {
             synchronized (room) {
             counter++;
        	}
 		}
 	}, "t1");
     Thread t2 = new Thread(() -> {
         for (int i = 0; i < 5000; i++) {
             synchronized (room) {
             counter--;
         }
     }
     }, "t2");
     t1.start();
     t2.start();
     t1.join();
     t2.join();
     log.debug("{}",counter);
}

synchronized principle

synchronized actually uses objects to ensure the atomicity of the code in the critical area. The code in the critical area is inseparable from the outside world and will not be interrupted by thread switching

synchronized is added to the method

    class Test{
        public synchronized void test() {

        }
    }
    //Equivalent to
    class Test{
        public void test() {
            synchronized(this) {

            }
        }
    }
//------------------------------------------------------------------------------------------------
    class Test{
        public synchronized static void test() {
        }
    }
   // Equivalent to
    class Test{
        public static void test() {
            synchronized(Test.class) {

            }
        }
    }

4.3 thread safety analysis of variables

4.3.1 thread safety analysis of member variables and static variables

  • If no variables are shared between threads, the variables are safe
  • If variables are shared between threads
    • Thread safe if there are only read operations
    • If there are read and write operations, this code is a critical area and thread safety needs to be considered

4.3.2 thread safety analysis of local variables

  • Local variables [local variables are initialized to basic data types] are safe
  • Objects referenced by local variables are not necessarily safe
    • If the object referenced by the local variable does not reference the object shared by the thread, it is thread safe
    • If the object referenced by a local variable references an object shared by a thread, thread safety should be considered

Thread safe situations

The local variable [local variable is initialized to basic data type] is safe, as shown in the following example

public static void test1() {
     int i = 10;
     i++;
}

When each thread calls the test1() method, multiple copies of the local variable i will be created in the stack frame memory of each thread, so there is no sharing

Thread unsafe condition

If the object referenced by the local variable escapes from the scope of the method, thread safety should be considered. The code example is Test15.java

public class Test15 {
    public static void main(String[] args) {
        UnsafeTest unsafeTest = new UnsafeTest();
        for (int i =0;i<100;i++){
            new Thread(()->{
                unsafeTest.method1();
            },"thread "+i).start();
        }
    }
}
class UnsafeTest{
    ArrayList<String> arrayList = new ArrayList<>();
    public void method1(){
        for (int i = 0; i < 100; i++) {
            method2();
            method3();
        }
    }
    private void method2() {
        arrayList.add("1");
    }
    private void method3() {
        arrayList.remove(0);
    }
}
Unsafe cause analysis

method2 and method3 in any thread refer to the list member variable in the same object: an ArrayList. When adding an element, it may be completed in two steps:

  1. Step 1: store this element in the arrayList[Size]; The second step is to increase the value of Size.
  2. In the case of single thread operation, if Size = 0, after adding an element, the element is at position 0 and Size=1; In the case of multithreading, for example, if there are two threads, thread A first stores the element in position 0. However, at this time, the CPU calls thread A to pause, and thread B gets A chance to run. Thread B also adds elements to this ArrayList because the Size is still equal to 0 (note that we assume that adding an element takes two steps, while thread A only completes step 1), so thread B also stores the elements in position 0. Then both thread A and thread B continue to run, increasing the value of Size. Well, now let's take A look at the ArrayList. In fact, there is only one element stored in position 0, and the Size is equal to 2. This is "thread unsafe".

resolvent

You can change the list to a local variable, so there won't be the above problem

class safeTest{
    public void method1(){
        ArrayList<String> arrayList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
        method2(arrayList);
        method3(arrayList);}
    }
    private void method2(ArrayList arrayList) {
        arrayList.add("1");
    }
    private void method3(ArrayList arrayList) {
        arrayList.remove(0);
    }
}
Think about the importance of private or final

Thinking brought by the method access modifier, if the methods of method2 and method3 are modified to public, will thread safety problems be caused? Case 1: other threads call method2 and method3; Case 2: on the basis of case 1, add a subclass for ThreadSafe class, and the subclass covers method2 or method3 methods, as shown below: from this example, we can see the significance of [security] provided by private or final. Please understand [Close] in the opening and closing principle

class ThreadSafe {
    public final void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }
    private void method2(ArrayList<String> list) {
        list.add("1");
    }
    private void method3(ArrayList<String> list) {
        list.remove(0);
    }
}
class ThreadSafeSubClass extends ThreadSafe{
    @Override
    public void method3(ArrayList<String> list) {
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}

4.3.3 common thread safety classes

  1. String
  2. Integer
  3. StringBuffer
  4. Random
  5. Vector
  6. Hashtable
  7. Classes under java.util.concurrent package

Here, they are thread safe, which means that when multiple threads call a method of their same instance, they are thread safe. It can also be understood that each of their methods is atomic

Hashtable table = new Hashtable();
new Thread(()->{
 	table.put("key", "value1");
}).start();
new Thread(()->{
 	table.put("key", "value2");
}).start();

Combination of thread safe class methods

However, note that the combination of their multiple methods is not atomic, as shown in the analysis below

Hashtable table = new Hashtable();
// Thread 1, thread 2
if( table.get("key") == null) {
 table.put("key", value);
}

Thread safety of immutable classes

String and Integer classes are immutable classes, because their internal state cannot be changed, so their methods are thread safe. Some students may have questions. String has methods such as replace and substring [can] change values. In fact, calling these methods returns a newly created object!

public class Immutable{
     private int value = 0;
     public Immutable(int value){
     this.value = value;
 	}
     public int getValue(){
         return this.value;
     }
     public Immutable add(int v){
         return new Immutable(this.value + v);
     }
}

Sample analysis - thread safe

Example 1

To analyze whether the thread is safe, first consider the member variables, class variables and local variables of the class. If the variables will be shared among threads, then thread safety must be considered. If variable A refers to an instance of the thread safety class and calls only one method of the thread safety class, then variable A is thread safe. The following is an analysis of example 1: this class is not thread safe. There is only one instance of the MyAspect class, and the member variable start will be read and written by multiple threads at the same time

@Aspect
@Component
public class MyAspect {
        // Is it safe?
        private long start = 0L;

        @Before("execution(* *(..))")
        public void before() {
            start = System.nanoTime();
        }

        @After("execution(* *(..))")
        public void after() {
            long end = System.nanoTime();
            System.out.println("cost time:" + (end-start));
        }
    }
Example 2

This example is a typical three-tier model call. There is only one instance of the MyServlet UserServiceImpl UserDaoImpl class. There is no member variable in the UserDaoImpl class. The object referenced by the variable in the update method is not shared by threads, so it is thread safe; There is only one thread safe instance of UserDaoImpl class in UserServiceImpl class, so UserServiceImpl class is thread safe. Similarly, MyServlet is thread safe

public class MyServlet extends HttpServlet {
 // Is it safe
 private UserService userService = new UserServiceImpl();

 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // Is it safe
 private UserDao userDao = new UserDaoImpl();
 public void update() {
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao {
 public void update() {
 String sql = "update user set password = ? where username = ?";
 // Is it safe
 try (Connection conn = DriverManager.getConnection("","","")){
 // ...
 } catch (Exception e) {
 // ...
 }
 }
}
Example 3

Similar to example 2, the UserDaoImpl class has a member variable, so multiple threads can operate on the member variable conn at the same time, so it is unsafe

public class MyServlet extends HttpServlet {
    // Is it safe
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    // Is it safe
    private UserDao userDao = new UserDaoImpl();
    public void update() {
        userDao.update();
    }
}
public class UserDaoImpl implements UserDao {
    // Is it safe
    private Connection conn = null;
    public void update() throws SQLException {
        String sql = "update user set password = ? where username = ?";
        conn = DriverManager.getConnection("","","");
        // ...
        conn.close();
    }
}
Example 4

Similar to example 3, UserDao exists as a local variable in the update method of UserServiceImpl class, so a new UserDao object will be created when each thread accesses. The new object is unique to the thread, so it is thread safe

public class MyServlet extends HttpServlet {
    // Is it safe
    private UserService userService = new UserServiceImpl();
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    public void update() {
        UserDao userDao = new UserDaoImpl();
        userDao.update();
    }
}
public class UserDaoImpl implements UserDao {
    // Is it safe
    private Connection = null;
    public void update() throws SQLException {
        String sql = "update user set password = ? where username = ?";
        conn = DriverManager.getConnection("","","");
        // ...
        conn.close();
    }
}
Example 5
public abstract class Test {
    public void bar() {
        // Is it safe
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        foo(sdf);
    }
    public abstract foo(SimpleDateFormat sdf);
    public static void main(String[] args) {
        new Test().bar();
    }
}

The behavior of foo is uncertain, which may lead to insecurity. It is called alien method, because foo method can be rewritten, resulting in thread insecurity. This is taken into account in the String class. The String class is finally, and subclasses cannot override its methods.

    public void foo(SimpleDateFormat sdf) {
        String dateStr = "1999-10-11 00:00:00";
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                try {
                    sdf.parse(dateStr);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

4.4 exercise analysis

Test16.java

4.5 Monitor concept

Java object header

Taking a 32-bit virtual machine as an example, the object header structure of an ordinary object is as follows, in which Klass Word is a pointer to the corresponding Class object;

Array object

The Mark Word structure is

Therefore, the structure of an object is as follows:

Monitor principle

Monitor is translated as monitor or tube

Each java object can be associated with a Monitor. If synchronized is used to lock the object (heavyweight), the Mark Word in the object header will be set as a pointer to the Monitor object

  • At the beginning, the Owner in Monitor is null
  • When Thread-2 executes the synchronized(obj) {} code, the Owner of the Monitor will be set to Thread-2. The lock is successful. There can only be one Owner in the Monitor at the same time
  • When Thread-2 occupies the lock, if Thread-3 and Thread-4 also execute the synchronized(obj) {} code, it will enter the EntryList and become BLOCKED
  • Thread-2 executes the contents of the synchronization code block, and then wakes up the waiting threads in the EntryList to compete for the lock. The competition is unfair
  • In the figure, Thread-0 and Thread-1 in the WaitSet are threads that have obtained locks before, but the conditions are not met to enter the WAITING state. They will be analyzed later when we talk about wait notify

Note: synchronized must enter the monitor of the same object to have the above effect. Objects that are not synchronized will not be associated with the monitor and do not comply with the above rules

synchronized principle

The code is as follows: Test17.java

    static final Object lock=new Object();
    static int counter = 0;
    public static void main(String[] args) {
        synchronized (lock) {
            counter++;
        }
    }

Decompiled partial bytecode

 0 getstatic #2 <com/concurrent/test/Test17.lock>
 # Get the reference of lock (synchronized starts)
 3 dup    
 # The value at the top of the copy operand stack is placed at the top of the stack, that is, a reference to lock is copied
 4 astore_1
 # The value at the top of the operand stack pops up, that is, the reference of lock is saved in the local variable table
 5 monitorenter
 # Set the Mark Word of the lock object as a pointer to Monitor
 6 getstatic #3 <com/concurrent/test/Test17.counter>
 9 iconst_1
10 iadd
11 putstatic #3 <com/concurrent/test/Test17.counter>
14 aload_1
# Get the reference of lock from the local variable table and put it at the top of the operand stack
15 monitorexit
# Reset the Mark Word of the lock object and wake up the EntryList
16 goto 24 (+8)
# The following is the exception handling instruction. You can see that if an exception occurs, the lock can also be released automatically
19 astore_2
20 aload_1
21 monitorexit
22 aload_2
23 athrow
24 return

Note: method level synchronized is not reflected in bytecode instructions

Advanced principle of synchronized

Lightweight Locking

The usage scenario of lightweight lock is: if an object has multiple threads to lock it, but the locking time is staggered (that is, no one can compete), lightweight lock can be used for optimization. The lightweight lock is transparent to the user, that is, the syntax is still synchronized. Suppose there are two methods to synchronize blocks and lock the same object

static final Object obj = new Object();
public static void method1() {
     synchronized( obj ) {
         // Synchronization block A
         method2();
     }
}
public static void method2() {
     synchronized( obj ) {
         // Synchronization block B
     }
}
  1. Each time you point to a synchronized code block, a Lock Record object will be created. Each thread will include a Lock Record structure. The Lock Record can store the object's Mark Word and object reference reference
  2. Let the Object reference in the lock record point to the Object, and try to replace the Mark Word of the Object object with cas(compare and sweep), and store the value of Mark Word in the lock record
  3. If cas replacement is successful, the object header of the object stores the address and status 01 of the lock record, as shown below
  4. If cas fails, there are two situations
    1. If other threads already hold the lightweight lock of the Object, it indicates that there is competition and the lock expansion phase will be entered
    2. If your own thread has executed synchronized locking, add another Lock Record as the count of reentry
  5. When the thread exits the synchronized code block, * * if a null lock record * * is obtained, it indicates that there is a reentry. At this time, the lock record is reset, indicating that the reentry count is reduced by one
  6. When the thread exits the synchronized code block, if the obtained lock admission value is not null, cas is used to restore the value of Mark Word to the object
    1. Successful unlocking
    2. Failure indicates that the lightweight lock has been expanded or upgraded to a heavyweight lock, and enters the heavyweight lock unlocking process

Lock expansion

If the cas operation fails in the process of trying to add a lightweight lock, it is a case that other threads have added a lightweight lock to the object. This is to expand the lock and turn the lightweight lock into a heavyweight lock.

  1. When Thread-1 performs lightweight locking, Thread-0 has already applied lightweight locking to the object
  2. At this time, Thread-1 fails to add a lightweight lock and enters the lock expansion process
    1. That is, apply for the Monitor lock for the Object, let the Object point to the heavyweight lock address, and then enter the EntryList of the Monitor to become BLOCKED
  3. When Thread-0 launches the synchronized synchronization block, use cas to restore the value of Mark Word to the object header. If it fails, it will enter the unlocking process of heavyweight lock, that is, find the Monitor object according to the Monitor address, set the Owner to null, and wake up the Thread-1 thread in the EntryList

Spin optimization

When heavyweight locks compete, spin can also be used for optimization. If the current thread spins successfully (that is, the thread holding the lock releases the lock when spinning), the current thread can obtain the lock without context switching

  1. Spin retry success
  2. When the spin retry fails, the thread that spins for a certain number of times still does not wait for the locked thread to release the lock

Spin will occupy CPU time. Single core CPU spin is a waste, and multi-core CPU spin can give play to its advantages. After Java 6, the spin lock is adaptive. For example, if the object has just succeeded in a spin operation, it is considered that the possibility of successful spin this time will be high, so spin more times; On the contrary, less spin or even no spin. In short, it is more intelligent. After Java 7, you can't control whether to turn on the spin function

Bias lock

In the lightweight lock, we can find that if the same thread needs to perform CAS operation when re entering the lock of the same 2 object, which is a bit time-consuming, java6 starts to introduce something biased towards the lock. Only when CAS is used for the first time, the Mark Word header of the object is set to the lock thread ID, and then the lock thread re enters the lock, If the thread ID is found to be its own, CAS is no longer required

Biased state

The process of creating an object

  1. If the bias lock is enabled (it is enabled by default), the value of the last three digits of Mark Word is 101 immediately after the object is created, and its Thread, epoch and age are all 0. Set these values when locking

  2. The bias lock is delayed by default and will not take effect immediately when the program starts. If you want to avoid the delay, you can add a virtual machine parameter to disable the delay: - XX:BiasedLockingStartupDelay=0 to disable the delay

  3. Note: after the object in the bias lock is unlocked, the thread id is still stored in the object header

  4. Experiment Test18.java, plus the virtual machine parameter - XX:BiasedLockingStartupDelay=0

    1. public static void main(String[] args) throws InterruptedException {
              Test1 t = new Test1();
              test.parseObjectHeader(getObjectHeader(t));
              synchronized (t){
                  test.parseObjectHeader(getObjectHeader(t));
              }
              test.parseObjectHeader(getObjectHeader(t));
          }
      
      1. The output results are as follows. The status code of the three outputs is 101
      biasedLockFlag (1bit): 1
      	LockFlag (2bit): 01
      biasedLockFlag (1bit): 1
      	LockFlag (2bit): 01
      biasedLockFlag (1bit): 1
      	LockFlag (2bit): 01
      

Test disabled: if the bias lock is not enabled, the value of the last three digits after the object is created is 001. At this time, its hashcode and age are all 0. Hashcode is assigned only when hashcode is used for the first time. When the above test code is running, add the VM parameter - XX:-UseBiasedLocking to disable the bias lock (if the bias lock is disabled, the lightweight lock is preferred), exit the synchronized state and change back to 001

  1. Test code Test18.java virtual machine parameter - XX:-UseBiasedLocking

  2. The output results are as follows: the initial state is 001, then the lightweight lock is added to 00, and finally it is restored to 001

    biasedLockFlag (1bit): 0
    	LockFlag (2bit): 01
    LockFlag (2bit): 00
    biasedLockFlag (1bit): 0
    	LockFlag (2bit): 01
    
Undo skew lock hashcode method

Test hashcode: when the hashcode method of the object is called, the bias lock of the object will be revoked, because there is no place to store the hashcode value when using the bias lock

  1. The test code is as follows. Use the virtual machine parameter - XX:BiasedLockingStartupDelay=0 to ensure that our program uses bias lock at the beginning! However, the results show that the program still uses lightweight locks. Test20.java

        public static void main(String[] args) throws InterruptedException {
            Test1 t = new Test1();
            t.hashCode();
            test.parseObjectHeader(getObjectHeader(t));
    
            synchronized (t){
                test.parseObjectHeader(getObjectHeader(t));
            }
            test.parseObjectHeader(getObjectHeader(t));
        }
    
  2. Output results

    biasedLockFlag (1bit): 0
    	LockFlag (2bit): 01
    LockFlag (2bit): 00
    biasedLockFlag (1bit): 0
    	LockFlag (2bit): 01
    
Undo bias lock - objects used by other threads

Here we demonstrate the process of turning partial lock revocation into a lightweight lock. Then we have to meet the use conditions of the lightweight lock, that is, no thread competes for the lock of the same object. We use wait and notify to assist the implementation

  1. Code Test19.java, virtual machine parameter - XX:BiasedLockingStartupDelay=0, ensure that our program uses bias lock at the beginning!

  2. The output result is that the bias lock is first used, but when the second thread attempts to obtain the object lock, it is found that the original object is biased to thread 1, then the bias lock will fail and the lightweight lock will be added

    biasedLockFlag (1bit): 1
    	LockFlag (2bit): 01
    biasedLockFlag (1bit): 1
    	LockFlag (2bit): 01
    biasedLockFlag (1bit): 1
    	LockFlag (2bit): 01
    biasedLockFlag (1bit): 1
    	LockFlag (2bit): 01
    LockFlag (2bit): 00
    biasedLockFlag (1bit): 0
    	LockFlag (2bit): 01
    
Undo - call wait/notify

It will turn the lock of the object into a heavyweight lock, because heavyweight locks are not supported until the wait/notify method

Batch re bias

If the object is accessed by multiple threads but there is no competition, then the object biased to thread 1 has the opportunity to re bias to thread 2, that is, it can not be upgraded to a lightweight lock, but this contradicts our previous experiments. In fact, there are conditions for re bias: when more than 20 objects revoke the bias to the same thread, such as thread 1, Then the 20th and later objects can change the action of revoking the bias to thread 1 to bias the 20th and later objects to thread 2. Test21.java

4.6 wait and notify

It is recommended to look at the javadoc documents of the wait and notify methods first

4.6.1 protective pause of synchronization mode

That is, Guarded Suspension, which is used to wait for the execution result of another thread. Key points:

  1. One result needs to be passed from one thread to another to associate them with the same GuardedObject
  2. If there are results continuously from one thread to another, you can use message queuing (see producer / consumer)
  3. In JDK, this mode is adopted for the implementation of join and Future
  4. Because we have to wait for the results of the other party, we are classified into synchronous mode

Code: Test22.java Test23.java this is with timeout

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

The enhancement of timeout by jiang'dao'de in Test23.java is reflected in the source code of join(long millis):

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
		
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
        // join a specified time
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

In the multi tasking version of GuardedObject, Futures is like mailboxes on the first floor of a residential building (each mailbox has a room number). t0, t2 and t4 on the left are like residents waiting for mail. t1, t3 and t5 on the right are like postmen. If you need to use GuardedObject objects among multiple classes, it is not convenient to pass them as parameters. Therefore, an intermediate class for decoupling is designed, This can not only decouple the result wait and result producer, but also support the management of multiple tasks at the same time. The difference between producer and consumer model is that there is a one-to-one correspondence between producer and consumer, but producer consumer model is not. This pattern is used in the calling of rpc framework. Test24.java

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

4.6.2 producer / consumer of asynchronous mode

main points

  1. Unlike the GuardObject in the previous protective pause, there is no need for one-to-one correspondence between the threads that generate and consume results
  2. Consumption queues can be used to balance thread resources for production and consumption
  3. The producer is only responsible for generating the result data and does not care about how to deal with the data, while the consumer focuses on dealing with the result data
  4. Message queues have capacity limits. When full, data will not be added, and when empty, data will not be consumed
  5. Various in JDK Blocking queue , this model is adopted

"Asynchronous" means that the message is not consumed immediately after the producer generates the message, while in "synchronous mode", the message is consumed immediately after it is generated.

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

We write a message queue for inter thread communication. Pay attention to the differences. Message frameworks such as rabbit mq are for inter process communication.

4.7 park & unpack

4.7.1 basic use

They are the Test26.java method in the LockSupport class

// Pauses the current thread
LockSupport.park();
// Resume a thread
LockSupport.unpark;

4.7.2 park unpark principle

Each thread has its own Parker object, which is composed of three parts_ counter, _ cond and_ mutex

  1. To make an analogy, thread is like a traveler, Parker is like his backpack, conditional variable_ cond is like a tent in a backpack_ counter is like the spare dry food in the backpack (0 is exhausted and 1 is sufficient)
  2. Call park to see if you need to stop and rest
    1. If the spare dry food runs out, get into the tent and rest
    2. If there is enough spare dry food, don't stop and move on
  3. Calling unpark is like making enough dry food
    1. If the thread is still in the tent, wake up and let him move on
    2. If the thread is still running at this time, the next time he calls park, he will only consume the spare dry food without staying and moving on
      1. Because the backpack space is limited, calling unpark multiple times will only supplement one spare dry food

You can look directly at the implementation process without looking at the examples

The procedure of calling park first and then upark

1. Call park first

  1. The current thread calls the Unsafe.park() method
  2. Check_ counter, this case is 0. In this case, the_ Mutex mutex (mutex object has a waiting queue _cond)
  3. Thread entry_ cond conditional variable blocking
  4. Set_ counter = 0

2. Call upark

  1. Call the Unsafe.unpark(Thread_0) method to set_ counter is 1
  2. Awaken_ Thread in cond condition variable_ 0
  3. Thread_0 resume operation
  4. Set_ counter is 0

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

The procedure of calling upark first and then park

  1. Call the Unsafe.unpark(Thread_0) method to set_ counter is 1
  2. The current thread calls the Unsafe.park() method
  3. Check_ counter, this case is 1. At this time, the thread does not need to be blocked and continues to run
  4. Set_ counter is 0

4.8 thread state transition

  1. RUNNABLE <–> WAITING

    1. After the thread obtains the object lock with synchronized(obj)
      1. When the obj.wait() method is called, the t thread starts from runnable -- > waiting
      2. When calling obj.notify(), obj.notifyAll(), t.interrupt()
        1. The contention lock succeeds, and the t thread starts running -- > from waiting
        2. Contention lock failed, t thread from waiting -- > blocked
    2. Test27.java
  2. RUNNABLE <–> WAITING

    1. Calling the LockSupport.park() method by the current thread will cause the current thread to start running -- > waiting
    2. Calling locksupport.unpark (target thread) or calling interrupt() of the thread will cause the target thread to change from waiting -- >
      RUNNABLE
  3. RUNNABLE <–> WAITING

    1. When the current thread calls the t.join() method, the current thread starts from runnable -- > waiting
      Note that the current thread is waiting on the monitor of the t thread object
    2. t when the thread ends running, or the interrupt() of the current thread is called, the current thread starts from waiting -- > runnable
  4. RUNNABLE <–> TIMED_WAITING

    After the t thread obtains the object lock with synchronized(obj)

    1. When the obj.wait(long n) method is called, the t thread starts from runnable -- > timed_ WAITING
    2. The waiting time of T thread exceeds n milliseconds, or when calling obj.notify(), obj.notifyAll(), t.interrupt()
      1. Contention lock successful, t thread from timed_ WAITING --> RUNNABLE
      2. Contention lock failed, t thread from timed_ WAITING --> BLOCKED
  5. RUNNABLE <–> TIMED_WAITING

    1. When the current thread calls the t.join(long n) method, the current thread starts from runnable -- > timed_ WAITING
      Note that the current thread is waiting on the monitor of the t thread object
    2. When the waiting time of the current thread exceeds n milliseconds, or the running of t thread ends, or the interrupt() of the current thread is called, the current thread starts from
      TIMED_WAITING --> RUNNABLE
  6. RUNNABLE <–> TIMED_WAITING

    1. The current thread calls Thread.sleep(long n). The current thread starts from runnable -- > timed_ WAITING
    2. The waiting time of the current thread exceeds n milliseconds or the interrupt() of the thread is called. The current thread starts from timed_ WAITING --> RUNNABLE
  7. RUNNABLE <–> TIMED_WAITING

    1. When the current thread calls LockSupport.parkNanos(long nanos) or LockSupport.parkUntil(long millis), when
      Program from runnable -- > timed_ WAITING
    2. Calling locksupport.unpark (target thread), calling thread interrupt(), or waiting for timeout will cause the target thread to
      TIMED_WAITING–> RUNNABLE

4.9 activity

A series of problems related to activity can be solved with ReentrantLock.

4.9.1 deadlock

There are such situations: A thread needs to obtain multiple locks at the same time. At this time, life and death locks are easy to occur. t1 thread obtains the lock of object A and then wants to obtain the lock of object B; t2 thread obtains the lock of B object. Next, it wants to obtain the lock instance of A object. Test28.java

4.9.2 deadlock detection

The jconsole tool can be used to detect deadlock; Or use jps to locate the process id and jstack to locate the deadlock: Test28.java

The following is a demonstration using the jstack tool

D:\My project\JavaLearing\java Concurrent programming\jdk8>jps
1156 RemoteMavenServer36
20452 Test25
9156 Launcher
23544 Jps
23848
22748 Test28

D:\My project\JavaLearing\java Concurrent programming\jdk8>jstack 22748
2020-07-12 18:54:44
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.211-b12 mixed mode):

"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000002a03800 nid=0x5944 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

//.................. Most of the contents are omitted//
Found one Java-level deadlock:
=============================
"Thread two":
  waiting to lock monitor 0x0000000002afc0e8 (object 0x00000000db9f76d0, a java.lang.Object),
  which is held by "Thread 1"
"Thread 1":
  waiting to lock monitor 0x0000000002afe1e8 (object 0x00000000db9f76e0, a java.lang.Object),
  which is held by "Thread two"

Java stack information for the threads listed above:
===================================================
"Thread two":
        at com.concurrent.test.Test28.lambda$main$1(Test28.java:39)
        - waiting to lock <0x00000000db9f76d0> (a java.lang.Object)
        - locked <0x00000000db9f76e0> (a java.lang.Object)
        at com.concurrent.test.Test28$$Lambda$2/326549596.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"Thread 1":
        at com.concurrent.test.Test28.lambda$main$0(Test28.java:23)
        - waiting to lock <0x00000000db9f76e0> (a java.lang.Object)
        - locked <0x00000000db9f76d0> (a java.lang.Object)
        at com.concurrent.test.Test28$$Lambda$1/1343441044.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)


4.9.3 dining problems

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

There are five philosophers sitting around the round table.
They only do two things, thinking and eating. They think for a while, have a meal, and then think after dinner.
When eating, you should use two chopsticks. There are five chopsticks on the table. Each philosopher has one chopstick on his left and right hands.
If the chopsticks are held by the people around you, you have to wait for Test29.java

When each philosopher thread holds a chopstick, they are waiting for another thread to release the lock, resulting in a deadlock. This kind of thread does not end as expected and cannot be executed. It is classified as [activity] problem. In addition to deadlock, there are also two kinds of situations: Live lock and hungry
condition

4.9.4 hunger

In many tutorials, hunger is defined as that a thread cannot be scheduled and executed by the CPU due to its low priority. Hunger is not easy to demonstrate. Hunger will be involved when talking about read-write locks. Next, I will talk about an example of thread hunger. Let's take a look at solving the previous deadlock problem by using sequential locking, Two threads lock two different objects in the same order. But there is hunger

Solution of sequential locking

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

4.10 ReentrantLock

Compared with synchronized, it has the following characteristics

  1. Interruptible
  2. You can set the timeout
  3. Can be set to fair lock
  4. Multiple condition variables are supported, that is, threads that do not meet the conditions can be placed in different collections to wait

Like synchronized, reentrant is supported

Basic grammar

// Acquire lock
reentrantLock.lock();
try {
 // Critical zone
} finally {
 // Release lock
 reentrantLock.unlock();
}

Reentrant

Reentrant means that if the same thread obtains the lock for the first time, it has the right to obtain the lock again because it is the owner of the lock. If it is a non reentrant lock, it will be blocked by the lock the second time it obtains the lock

Interruptible

Take a direct look at the example: Test31.java

Lock timeout

Look directly at the example: Test32.java

Use lock timeout to solve deadlock problem: Test33.java

Fair lock

In the synchronized lock, the lock waiting in the entrylist is not obtained on a first come first served basis during competition. Therefore, the synchronized lock is unfair; The ReentranLock lock is unfair by default, but it can be set to achieve a fair lock. The original intention is to solve the hunger problem mentioned earlier, but fair lock is generally not necessary, which will reduce the concurrency. trylock can also be used.

Conditional variable

There are also conditional variables in synchronized, that is, the waitSet lounge when we talk about the principle. When the conditions are not met, enter the waitSet and wait
The advantage of ReentrantLock's conditional variables over synchronized is that it supports multiple conditional variables, which is like

  1. synchronized means that those threads that do not meet the conditions are waiting for messages in a lounge
  2. ReentrantLock supports multiple lounges, including a lounge dedicated to waiting for cigarettes and a lounge dedicated to waiting for breakfast. It is also called according to the lounge when waking up
    wake up

Key points: Test34.java

  1. You need to obtain a lock before await
  2. After await is executed, it will release the lock and enter the conditionObject to wait
  3. The await thread is awakened (or interrupted, or timed out) to re compete for the lock lock. The wake-up thread must obtain the lock first
  4. After the contention lock is successful, the execution continues after await

Sequential control of synchronous mode

  1. Fixed running sequence, for example, 2 must be printed before 1
    1. wait notify Test35.java
    2. Park Unpark Test36.java
  2. Output alternately, thread 1 outputs a 5 times, thread 2 outputs b 5 times, and thread 3 outputs c 5 times. Now you need to output abcabcabcabcabc. How do you implement abcabc
    1. wait notify Test37.java
    2. Lock conditional variable version Test38.java
    3. Park Unpark Test39.java

Summary of this chapter

What we need to focus on in this chapter is

  1. Analyze which code fragments belong to critical areas when multithreading accesses shared resources
  2. Using synchronized mutex to solve thread safety problems in critical areas
    1. Master the syntax of synchronized lock object
    2. Master the syntax of synchronized loading member method and static method
    3. Master the wait/notify synchronization method
  3. Using lock mutual exclusion to solve the thread safety problem of critical area
    Master the use details of lock: interruptible, lock timeout, fair lock and condition variable
  4. Learn to analyze the thread safety of variables and master the use of common thread safety classes
  5. Understand thread activity issues: deadlocks, livelocks, starvation
  6. Application aspect
    1. Mutual exclusion: use synchronized or Lock to achieve the mutual exclusion effect of shared resources, achieve atomic effect and ensure thread safety.
    2. Synchronization: use the condition variable of wait/notify or Lock to achieve the effect of inter thread communication.
  7. Principle aspect
    1. monitor, synchronized, wait/notify principles
    2. synchronized advanced principle
    3. Park & unpark principle
  8. Mode aspect
    1. Protective pause in synchronization mode
    2. Producer consumer of asynchronous mode
    3. Sequential control of synchronous mode

problem

  1. Error Test24.java encountered during collection concurrency Reference blog, not read

Tags: Java Microservices

Posted on Tue, 30 Nov 2021 19:13:45 -0500 by Qlubbie