Multithreading [advanced version]

Table of contents

1. Common lock strategies

1.1 Optimistic lock and pessimistic lock

1.2 Lightweight locks and heavyweight locks

1.3 Spin lock and pending lock

1.4 Mutex locks and read-write locks

1.5 Reentrant locks and non-reentrant locks

1.6 Fair locks and unfair locks

1.7 Related interview questions about lock strategies

2. CAS

3. Synchronized principle

3.1 Basic features

3.2 Locking steps

3.3 Lock Elimination 

3.4 Lock coarsening 

3.5 Common Components of JUC

 4. Thread-safe collection classes

4.1 The difference between HashTable and ConcurrentHashMap

4.2 Multithreading-related interview questions


1. Common lock strategies

1.1 Optimistic lock and pessimistic lock

When it comes to the two concepts of optimism and pessimism, everyone is familiar with them. We often face optimism and pessimism in our lives, but we look at it from our own perspective. Some people look at one thing differently. It is considered optimistic, while some people think he is pessimistic; the "optimistic" and "pessimistic" here are also very similar to the optimistic lock and pessimistic lock we said;

Optimistic lock : Every time you go to get the data, you think that others will not modify it, so it will not be locked, but when updating, you will judge whether others have updated the data during this period;

For example: Suppose there are two threads A and B, they want to get the data, because they are optimistic, so the two parties will not think that they are going to modify the data, so they will execute their own things after getting the data, and One feature is that before threads A and B update the shared data, they have to judge whether the shared data has been modified by other threads. If not, then directly update the value of the shared variable in memory. Will report an error or perform other related operations


Pessimistic locking : Every time you go to get the data, you think that others will modify it, so every time you get the data, you will lock it, so that if others want to get the data, they will block until it gets the lock;

For example: there are still two threads A and B, A and B are going to get data, because it is pessimistic, so it needs to be locked when getting data, assuming A gets the lock, then B will enter the blocking wait state, knowing that A releases the lock, the CPU will wake up the waiting thread B, and B can get the lock to operate on the data;


Generally speaking, pessimistic locks generally have to do more work, and the efficiency will be lower; while optimistic locks have to do less, and the efficiency is higher; 


1.2 Lightweight locks and heavyweight locks

Lightweight lock: faster and more efficient in the process of locking and unlocking;

Heavyweight lock: the process of locking and unlocking is slower and less efficient;

From this point of view, although lightweight and heavyweight are not the same thing as optimism and pessimism, there is a certain similarity. It can be considered that an optimistic lock may be a lightweight lock, but it is not absolute; will elaborate;


1.3 Spin lock and pending lock

A spin lock is a representative implementation of a lightweight lock. When a thread tries to acquire a lock, if the lock has been acquired (occupied) by someone else at this time, the thread cannot acquire the lock. , the thread will wait and try to fetch again after a certain interval. This mechanism of using cyclic locking -> waiting is called自旋锁(spinlock);

Advantages: Once the spin lock is released, you can get the lock immediately. The spin lock is a pure user mode operation, so the speed is very fast;

Disadvantage: Waiting all the time will consume CPU resources

Suspension and waiting locks are always representative implementations of heavyweight locks. When a thread does not apply for a lock, the thread will be suspended at this time, that is, it will be added to the waiting queue to wait. When the lock is released, it will be woken up and re-compete for the lock;

Advantages: No need to wait blindly, you can participate in other things during the waiting process, and make full use of CPU resources;

Disadvantages: If the lock is released, the lock cannot be obtained immediately, and the pending lock is implemented through the kernel mechanism, so the time will be longer and the efficiency will be lower;


1.4 Mutex locks and read-write locks

Mutex lock: Mutex lock is a very domineering existence. For example, there are threads A and B. After thread A locks successfully, the mutex lock has been monopolized by thread A at this time. As long as thread A does not release the lock in its hand, Thread B will fail to lock, so the CPU will be released to other threads. Since thread B has released the CPU, the code for thread B to lock will naturally be blocked.

The Synchronized we have learned is a mutex;

The phenomenon of blocking due to mutex locking failure is implemented by the operating system kernel. When the lock fails, the kernel will put the thread into a "sleep" state. After the lock is released, the kernel will wake up the thread at an appropriate time. When the thread successfully acquires the lock, it can continue to execute. As shown below:

Note: The biggest difference between mutex and spin lock:

  • After the mutex lock fails, the thread will release the CPU to other threads;
  • After the spin lock lock fails, the thread will wait busy until it gets the lock;

Read-write lock: It consists of two parts: a read lock and a write lock. If you only read shared resources, use a read lock to lock them. If you want to modify shared resources, use a write lock to lock them.

There are generally three types of read-write locks:

1. Lock the read

2. Lock the write

3. Unlock

Conventions in read-write locks:

  • There will be no lock competition between read locks and read locks, and no blocking waits will occur
  • Between write locks and write locks, there is lock competition
  • There is lock competition between read locks and write locks

1.5 Reentrant locks and non-reentrant locks

For a thread, for a lock, lock twice in a row. If there is a deadlock, it is a non-reentrant lock. If there is no deadlock, it is a reentrant lock;

Object locker = new Object();

synchronized(locker){
     synchronized(locker){
   }
}

The code like the above is the case of locking twice. The second lock needs to wait for the first lock to be released, and the first lock is released, and it needs to wait for the second lock to be successfully locked, so this situation is contradictory. But there will be no real deadlock, because synchronized is a reentrant lock. When adding a lock, it will first determine whether the thread currently trying to apply for the lock already owns the lock. If so, there will be no contradiction;

Both synchronized and ReentrantLock are reentrant locks, and the biggest meaning of reentrant locks is to prevent deadlocks;


1.6 Fair locks and unfair locks

 First of all, from the literal meaning, the thread that arrives first will obtain resources first, and the thread that arrives later will wait in line. This is fair, and unfair locks do not follow this principle. In fact, it is easy to understand. See the figure below:

This situation is fair and follows the first-come, first-served rule;

 And a situation like this is unfair, and there is a phenomenon of "jumping in line";


1.7 Related interview questions about lock strategies

1. How do you understand optimistic locking and pessimistic locking, and how do you implement them?

Pessimistic locks believe that multiple threads access the same shared variable with a high probability of conflict, and will actually lock it before each access to the shared variable;

Optimistic locking believes that the probability of conflict between multiple threads accessing the same shared variable is low, and it will not actually lock it, but directly try to access the data. While accessing, identify whether the current data has an access conflict;

The realization of pessimistic locking is to add a lock first (for example, with the help of the mutex provided by the operating system), and then operate the data after obtaining the lock. Wait if the lock cannot be obtained;

2. Introduce read-write lock?

Read-write lock is to lock the read operation and write operation separately;

There is no mutual exclusion between read locks and read locks;

Mutual exclusion between write lock and write lock;

Mutual exclusion between write lock and read lock;

Read-write locks are mainly used in the "frequent read, infrequent write" scenario;

3. What is a spin lock, why use the spin lock strategy, and what are the disadvantages?

If the lock acquisition fails, try to acquire the lock again immediately, and loop infinitely until the lock is acquired. If the lock acquisition fails for the first time, the second attempt will come in a very short time. Once the lock is released by other threads, you can Acquire the lock at the first time.

Advantages: No CPU resources are given up. Once the lock is released, the lock can be acquired at the first time, which is more efficient. It is very useful in scenarios where the lock holding time is relatively short. Disadvantage: If the lock is
held If the time is long, CPU resources will be wasted


4. Is synchronized a reentrant lock?
It is a reentrant lock.
A reentrant lock means that two consecutive locks will not cause deadlock

5. The characteristics of synchronized:

  1. Both optimistic locking and pessimistic locking
  2. Both a lightweight lock and a heavyweight lock
  3. Lightweight locks are implemented based on optional locks, and heavyweight locks are implemented based on pending locks
  4. not read-write lock
  5. is a reentrant lock
  6. is an unfair lock


2. CAS

1. Concept

Full name Compare and swap, compare  the data values ​​in  memory A  and  register R  , if the values ​​are the same, exchange the values ​​in memory B and  register R  ;

2. Features

The biggest feature of CAS is that a CAS operation is a single CPU instruction, which is atomic, so even if CAS does not lock, it can also ensure thread safety;

A class is provided for CAS in JDK1.8:


ABA questions in CAS:

The ABA question is a high-frequency interview question in CAS. We all know that CAS compares the values ​​of memory and registers to see if they are the same. It is through this comparison to detect whether the memory has changed, either the same, or different, different. They are all different, but there is a kind of similarity that is not the same in the true sense, but it is not sure whether the value has changed in the middle, and the original thing has been changed, but it has changed back to the original state, assuming that it was originally a, and then changed to b , and later turned into a, this is the problem of a->b->a;


How to solve the ABA problem? ? ?

The ABA problem is essentially a problem of repeated horizontal jumps. As long as we agree that the data can only be changed unilaterally, either the data can only increase or the data can only decrease, then the problem will be solved;

If we require that the data can be both increased and decreased, we can agree on a version number variable, the agreed version number can only increase, and every time it is modified, a version number will be added, so that we use the version number every time we compare To compare, instead of comparing the value itself, this can also solve the problem very well;


3. Synchronized principle

3.1 Basic features

  • 1. At the beginning, it is an optimistic lock. If there are frequent lock conflicts, it will be converted to a pessimistic lock.
  • 2. At the beginning, it is a lightweight lock implementation. If the lock is held for a long time, it will be converted into a heavyweight lock.
  • 3. The spin lock strategy that is most likely to be used when implementing lightweight locks
  • 4. It is an unfair lock
  • 5. It is a reentrant lock
  • 6. Not a read-write lock

3.2 Locking steps

process:

  1. At first there was no lock;
  2. At the beginning of locking, it is a biased lock state;
  3. In case of lock competition, it will be transformed into a lightweight lock state (spin lock);
  4. When the competition is fierce, it will become a heavyweight lock, and this process is handed over to the kernel to block and wait;

About biased locks:

Biased locks are not really locked, but simply marked, if you want to own this lock, if there is no lock competition, then no lock, if there are other threads competing for this lock, then upgrade to lightweight Lock, which not only ensures efficiency, but also ensures thread safety;


3.3 Lock Elimination 

Lock elimination is an optimization method done in the compilation stage, which is used to detect whether the current code is a multi-threaded task or whether it is necessary to add a lock. If it is not necessary, the lock is written again, and the lock will be automatically removed during the compilation process;

If we use the synchronized keyword to lock an operation when thread safety is not involved, then the lock will be automatically eliminated during the compilation phase. The lock elimination mechanism is an intelligent operation, and it will According to different codes, to determine whether the current operation needs to be locked, if not, it will be automatically eliminated;


3.4 Lock coarsening 

When it comes to the coarsening of locks, we must first mention a concept called the granularity of locks. The granularity of locks is the amount of code contained in a synchronized code block. It is generally believed that the more codes, the coarser the granularity, and the fewer codes, the finer the granularity;

Usually when writing code, we hope that the granularity of the lock is smaller, because there are fewer serial codes and more codes that are executed concurrently;

for example:

Suppose we call the leader to report 3 jobs, there are two situations:

1. Make a phone call first, report the progress of A, and then hang up

    Make another call, report on B's progress, hang up again

    Make another call, report C's progress, hang up again

Every time the leader answers the phone, it is a locking process. If someone (other thread) wants to call the leader, it is in a blocked waiting state, and hanging up the phone is to release the lock; when you hang up, you want to report to work B You may not be able to enter the progress, the leader may be talking with someone else, so if you want to enter again, you have to block and wait for a while. This process is equivalent to splitting the granularity of the lock into finer, but It may block and wait every time, so the efficiency is not high, and other problems may also occur concurrently;

2. Make a phone call and directly report the work progress of A, B, and C to the leader at one time;

In this way, the consumption of blocking and waiting is avoided, and the efficiency is greatly improved;


3.5 Common Components of JUC

JUC is the abbreviation of Java.util.concurrent, here is a class of multi-threaded concurrency;

1. Callable interface

Here is a program to implement this interface:

public class Demo16 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建任务,计算从 1 加到 100 的和
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 100; i++) {
                    sum += i;
                }
                return sum;
            }
        };
        // 创建一个线程来完成这个任务
        // Thread 不能直接传 callable, 外面需要再包装一层
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread();
        thread.start();
        System.out.println(futureTask.get());

    }
}

The above code is also a method of thread creation;


2. ReentrantLock class

Reentrant mutex locks are similar to synchronized positioning, and are used to achieve mutual exclusion effects and ensure thread safety;

However, synchronzied controls locking and unlocking based on code block implementation, while ReentrantLock provides lock and unlock methods for locking and unlocking;

Although synchronized has been used in most cases, ReentrantLock also has its own special method:

  1. When using synchronized locking, if the lock is found to be occupied, it will enter the blocking state, and ReentrantLock provides a tryLock method. If the lock is found to be occupied, it will not block and return false directly;
  2.  Synchronized is an unfair lock (does not follow the rules), and ReentrantLock provides two working modes: fair and unfair;
  3.  Synchronized works with wait and notify to wake up and wait. If multiple threads wait for the same object, one will be woken up randomly during notify, while ReentrantLock works with Condition to wake up and wait, and it can specify a thread to wake up;

 4. Thread-safe collection classes

4.1  The difference between HashTable and ConcurrentHashMap

Our commonly used classes such as ArrayList, LinkedList, HashMap, etc. are not thread-safe, so if we want to use them in a multi-threaded environment, problems may arise;

In response to this situation, the standard library provides a thread-safe version of the collection class. As far as I know, starting from the early thread-safe collections, they are Vector and HashTable:

Vector:

Similar to ArrayList, Vector is a variable-length array. Unlike ArrayList, Vector is thread-safe, and it adds the synchronized keyword to almost all public methods. Due to the performance degradation caused by locking , this mandatory synchronization mechanism is redundant when there is no need to access the same object concurrently, so now  Vector has been deprecated;

CopyOnWriteArrayList and CopyOnWriteArraySet:

They are ArrayList and ArraySet with write locks , which lock the entire object, but read operations can be executed concurrently;

HashTable:

HashTable is similar to HashMap, the difference is that HashTable is thread-safe, it adds the synchronized keyword to almost all public methods, and another difference is that the K and V of HashTable cannot be null, but HashMap can, it is now Also deprecated for performance reasons ;

The difference between HashTable and ConcurrentHashMap:

HashTable locks the entire hash table, and any CURD operation may trigger locks, or there may be lock competition; while ConcurrentHashMap locks each linked list, and each operation is performed on the corresponding linked list. Lock, operating different linked lists is to lock different lock objects. At this time, there will be no lock conflicts. If there is no lock conflict, there will be no blocking and waiting, which also improves efficiency;


4.2 Multithreading-related interview questions

(1) Talk about the usage of the volatile keyword?

volatile can guarantee memory visibility and force data to be read from the main memory. At this time, if other threads modify the variable modified by volatile, the latest value can be read immediately;
 

(2) How does Java multithreading realize data sharing?
The JVM divides memory into these areas: method area, heap area, stack area, and program counter. The memory area of ​​the heap area is shared among multiple threads. As long as a certain data is placed in the heap memory, it can be accessed by multiple threads;


(3) How many states does a Java thread have? How to switch between states?

  • NEW: The work has been arranged, but the action has not yet started. The newly created thread is in this state when the start method has not been called;
  • RUNNABLE: Workable. It can be divided into working and about to start. After calling the start method, it is running on the CPU/is about to run;
  • BLOCKED: When using synchronized, if the lock is occupied by other threads, it will block and wait, thus entering this state;
  • WAITING: Calling the wait method will enter this state;
  • TIMED_WAITING: Calling the sleep method or wait (timeout) will enter this state;
  • TERMINATED: The work is completed. When the thread run method is executed, it will be in this state;

(4) What is the difference and connection between Thread and Runnable?

      The Thread class describes a thread, and Runnable describes a task. When creating a thread, you need to specify the task to be completed by the thread. You can directly override the run method of Thread, or use Runnable to describe this task;


(5) What is the difference between a process and a thread?

  • Processes contain threads. Each process has at least one thread, the main thread.
  • The memory space is not shared between processes. The threads of the same process share the same memory space.
  • A process is the smallest unit of resource allocation by the system, and a thread is the smallest unit of system scheduling

Guess you like

Origin blog.csdn.net/m0_63635730/article/details/130303315