Preparation and summary of interview knowledge points - (concurrent articles)

foreword

This article mainly records the dialysis of knowledge points involved in interview questions related to Java concurrency, such as thread state, thread pool, volatile introduction, optimistic lock and pessimistic lock, Hashtable, ConcurrentHashMap, ThreadLocal understanding, etc., and new interview questions will be encountered later I will continue to improve and add details. Finally, thank you for your reading, and I hope you will gain something in the end.

Basic article address: Basics of Java Interview Preparation

Virtual machine article address: Virtual Machines for Java Interview Preparation


1. What are the states of the thread

Java threads are divided into six states, which are new, runnable, terminated, blocked, waiting, and time-limited waiting

The first three new creation, running, and termination are all one-way irreversible, while the latter blocking, waiting, and time-limited waiting can be changed back and forth with the running state

insert image description here


2. The core parameters of the thread pool

There are 7 core parameters of the thread pool

  • 核心线程数, refers to the number of threads that have been kept in the thread pool
  • 最大线程数, refers to the sum of the number of core threads and emergency threads
  • 生存时间, refers to the survival time of the emergency thread. When the idle time of the emergency thread reaches this survival time, it will end
  • 时间单位, refers to the unit of survival time, whether it is seconds or minutes or hours
  • 阻塞队列, when the core threads are used up, the remaining tasks are queued in the blocking queue, here refers to the length of the blocking queue
  • 线程工厂, create a new thread for the thread pool
    insert image description here

The last one is the rejection strategy . There are four kinds of rejection strategies, as follows

insert image description here

When the number of tasks exceeds the sum of the number of core threads, the maximum number of queues and the number of temporary threads, and the full load, the redundant tasks will be discarded or processed according to the rejection policy of the thread pool


3. The difference between sleep and wait

One commonality, three differences

common ground

The effects of wait(), wait(long) and sleep(long) are to make the current thread temporarily give up the right to use the CPU and enter the blocking state

difference

  • Method attribution is different

    • sleep(long) is a static method of Thread
    • And wait(), wait(long) are all member methods of Object, each object has
  • Wake up at different times

    • Threads executing sleep(long) and wait(long) will wake up after waiting for the corresponding milliseconds
    • wait(long) and wait() can also be woken up by notify, if wait() does not wake up, it will wait forever
    • They can all be interrupted to wake up
  • Different lock characteristics (emphasis)

    • The invocation of the wait method must first acquire the lock of the wait object , while sleep has no such restriction
    • After the wait method is executed, the object lock will be released, allowing other threads to acquire the object lock (I give up the cpu, but you can still use it)
    • And if sleep is executed in a synchronized code block, it will not release the object lock (I give up the cpu, and you can't use it)
      (it is simple and understandable here, wait releases the lock to sleep, sleep holds the lock to sleep)

insert image description here


4. Similarities and differences between lock and synchronized

can be broken down into three levels

difference

① Grammatical level

  • synchronized is a keyword, the source code is in jvm, implemented in c++ language
  • Lock is an interface, the source code is provided by jdk and implemented in java language
  • When using synchronized, the exit synchronization code block lock will be released automatically, but when using Lock, you need to manually call the unlock method to release the lock

②Functional level

  • Both belong to pessimistic locks, and both have basic functions of mutual exclusion, synchronization, and lock reentry (multiple locks can be added)
  • Lock provides many functions that synchronized does not have, such as obtaining the waiting state (which threads are blocked), fair lock, interruptible, timeout (the maximum timeout period can be set, and the acquisition lock is given up after the specified time), multiple condition variables
  • Lock has implementations suitable for different scenarios , such as ReentrantLock (reentrant lock), ReentrantReadWriteLock (scenario with more reads and fewer writes)

③Performance level

  • When there is no competition (or when there is little competition), synchronized has done a lot of optimizations, such as biased locks, lightweight locks, and good performance
  • Lock implementations generally provide better performance when contention is high

Lock lock is to use the created lock object to call lock(), unlock() method, lock and release lock
synchronized, which is a way to synchronize code blocks or synchronize methods


fair lock

  • Fair embodiment of fair lock
    • Threads already in the blocking queue (regardless of timeout) are always fair, first in first out
    • Fair lock refers to threads that are not in the blocking queue to compete for the lock. If the queue is not empty, wait honestly to the end of the queue
    • Unfair lock means that threads that are not in the blocking queue compete for the lock, and compete with the thread awakened by the queue head. Whoever grabs it counts.
  • Fair locks will reduce throughput and are generally not used

condition variable

  • The function of the condition variable in ReentrantLock is similar to the normal synchronized wait and notify, which is used in the linked list structure for temporary waiting when the thread obtains the lock and finds that the condition is not satisfied
  • The difference from the synchronized waiting set is that there can be multiple condition variables in ReentrantLock, which can achieve finer waiting and wake-up control

5. Can volatile guarantee thread safety?

There are three aspects to consider in thread safety: visibility, order, and atomicity

① Visibility means that when one thread modifies a shared variable, another thread can see the latest result

② Order means that the code in a thread is executed in the order in which it is written

③Atomicity means that multiple lines of code in one thread run as a whole, during which no code from other threads can jump into the queue


atomicity

  • Cause: Under multi-threading, the instructions of different threads are interleaved, which leads to the confusion of reading and writing of shared variables
  • Solution: Use pessimistic locks or optimistic locks, volatile cannot solve atomicity

visibility

  • Cause: Modifications to shared variables due to compiler optimizations, or cache optimizations, or CPU instruction reordering optimizations that are not visible to other threads
  • Solution: Decorating shared variables with volatile can prevent optimizations such as compilers from occurring, and make the modification of shared variables by one thread visible to another thread

The following is a typical forever loop - visibility case

insert image description here
So what is the reason that after thread 0 modifies the stop value, the main thread still continues to loop, and the stop obtained by thread 1 is displayed as true?

Among them is the role of JIT. JIT is an optimizer that optimizes hot bytecode files, such as frequently called methods and repeatedly executed loops;

When thread 0 has not changed the value of stop, the main thread has executed millions of times within 100 milliseconds of thread 0 sleep. At this time, the optimizer JIT can't sit still, so it reads repeatedly stop and Optimize the bytecode with the same value every time it is read to avoid frequent reading from memory, consider stop to be false, compile its bytecode into machine code and cache it, and when it loops again, just take this directly Machine code, saving intermediate interpretation process. As shown below

insert image description here


If the above stop variable is modified with volatile, JIT will not optimize it, and let its
orderliness be ignored

  • Cause: Due to compiler optimization, or cache optimization, or CPU instruction reordering optimization, the actual execution order of instructions is inconsistent with the writing order
  • Solution: Modifying shared variables with volatile will add different barriers when reading and writing shared variables, preventing other read and write operations from crossing the barriers, thereby achieving the effect of preventing reordering
  • Notice:
    • The barrier for writing to volatile variables isPrevent other write operations above from crossing the barrierArrange to write below the volatile variable
    • The barrier added to the read of a volatile variable isPrevent other read operations below from crossing the barrierqueue above volatile variable reads
    • The barriers added by volatile reads and writes can only prevent instruction reordering within the same thread

Write means that the upper operation cannot go over the bottom (to avoid being overwritten by the data before the program), and read means that the lower operation cannot go over the top (to avoid reading the data behind the program)

It cannot be arranged against the arrow
insert image description here


6. The difference between pessimistic locking and optimistic locking

Comparing pessimistic locking and optimistic locking

  • Representatives of pessimistic locks are synchronized and Lock locks

    • The core idea is [Threads can only operate shared variables if they own the lock. Only one thread can successfully occupy the lock each time, and the thread that fails to acquire the lock must stop and wait]
    • The thread from running to blocking, and then from blocking to waking up involves thread context switching. If it happens frequently, it will affect performance
    • In fact, when a thread acquires synchronized and Lock locks, if the lock is already occupied, it will do several retries to reduce the chance of blocking
  • The representative of optimistic lock is AtomicInteger,Use cas ( compareAndSetInt method, short for compare and exchange) to ensure atomicity

    • Its core idea is [no need to lock, only one thread can successfully modify the shared variable each time, other failed threads do not need to stop, keep retrying until success ]
    • Since the thread is always running, there is no need to block, so there is no thread context switching involved
    • It requires multi-core cpu support, and the number of threads should not exceed the number of cpu cores

The cas method starts from an optimistic point of view, assuming that no one else will modify the data every time it is acquired, so it will not be locked. It's just that when the shared data is modified, it will check and compare the old value passed in with the latest value of the current shared variable to determine whether others have modified the data.

If someone else modified it, then I get the latest value again.

If others have not modified it, then I directly modify the value of the shared data now. (Optimistic lock)

CAS is to be used together with volatile, so as to ensure that the shared variable you see every time is the latest value


Synchronized locking is a mutual exclusion method used to execute multiple lines of code as a whole to ensure atomicity under concurrency

However, CAS does not have mutual exclusion and is executed concurrently. It just uses the principle of comparison and exchange when modifying operations to determine whether the old value passed in is consistent with the latest value currently obtained (see if the value you want to update has been Modified), the modification is successful if it is consistent, and the modification fails if it is inconsistent.It doesn’t matter if the modification fails, because the optimistic lock keeps retrying until it succeeds. In the while (true) loop, the new value is obtained and the update operation is performed on the basis of the latest value.

Here is the code demo

synchronized pessimistic lock

insert image description here


CAS optimistic lock

insert image description here


7. The difference between Hashtable and ConcurrentHashMap

Hashtable vs ConcurrentHashMap

  • Both Hashtable and ConcurrentHashMap are thread-safe Map collections
  • Hashtable has low concurrency, and the entire Hashtable corresponds toa lock, at the same time, only one thread can operate it
  • ConcurrentHashMap has high concurrency, and the entire ConcurrentHashMap corresponds tomultiple locks, as long as the threads access different locks, there will be no conflict

(The underlying implementation of Hashtable is implemented by an array + linked list structure; the capacity of hashtable is a prime number, which has better distribution and does not require secondary hashing)


8. The difference between ConcurrentHashMap1.7 and 1.8

ConcurrentHashMap 1.7

  • Data structure: Segment(大数组) + HashEntry(小数组) + 链表, each segment corresponds to a lock, if multiple threads access different segments, there will be no conflict
  • Concurrency: The size of the Segment array is the concurrency, which determines how many threads can access concurrently at the same time. The Segment array cannot be expanded, which means that the concurrency is fixed when ConcurrentHashMap is created
  • index calculation
    • Suppose the length of the large array is 2 m 2^m2m , the index of the key in the large array is the high m bits of the secondary hash value of the key (for example: capacity is 32, 32 is 2 to the 5th power, take the high 5 bits of the secondary hash value, which is the first 5 bits, turn decimal to get the storage location index value)
    • Suppose the length of the small array is 2 n 2^n2n , the index of the key in the small array is the lower n bits of the secondary hash value of the key
  • Expansion: The expansion of each small array is relatively independent. When the small array exceeds the expansion factor, the expansion will be triggered , and the expansion will be doubled each time;Since ConcurrentHashMap1.7 is locked, even if the linked list uses the head insertion method, it will not cause a dead chain like HashMap1.7
  • Segment[0] prototype: When creating other small arrays for the first time, this prototype will be used as the basis. The length of the array and the expansion factor will be based on the prototype. The following figure is a demonstration of the new small array based on the current segment[0] as the prototype
    insert image description here
    . create
  • The minimum capacity of the small array is 2; the capacity of the small array = the capacity of the large array / the number of concurrency

Here is a picture, there is a picture and the truth.
The concurrency determines the size of the blue array, and the capacity determines the size of the small array entry.
insert image description here


ConcurrentHashMap 1.8

  • data structure: Node 数组 + 链表或红黑树,Each head node of the array acts as a lock, if the head nodes accessed by multiple threads are different, there will be no conflict. If competition occurs when generating the head node for the first time, use cas instead of synchronized to further improve performance
    insert image description here

  • Concurrency: The size of the Node array is the same as the concurrency . Unlike 1.7, the Node array can be expanded

  • Expansion conditions:The Node array will be expanded when it is full of 3/4, and the capacity will be expanded when it exceeds 1.7

  • Expansion unit: use the linked list as a unit to migrate the linked list from the back to the front. After the migration is completed, replace the old array head node with ForwardingNode

  • Concurrent get during expansion

    • According to whether it is ForwardingNode to decide whether to search in the new array or in the old array, it will not block
      insert image description here

    • If the length of the linked list exceeds 1, you need to copy the node (create a new node), fearing that the next pointer will change after the node migration

    • If the index of the last few elements of the linked list remains unchanged after expansion, the node does not need to be copied
      insert image description here

  • Concurrent put during capacity expansion

    • If the put thread is the same linked list as the expansion thread operation, the put thread will block
    • If the linked list of the put thread operation has not been migrated, that is, the head node is not ForwardingNode, it can be executed concurrently
    • If the linked list of the put thread operation has been migrated, that is, the head node is ForwardingNode, it can assist in expansion
  • capacity represents the estimated number of elements, and capacity / factory is used to calculate the initial array size, which needs to be close to 2 n 2^n2n

For example, to put 16 elements (capacity is 16), but the expansion factor of the array will be expanded when it is full. If the length of the array is 16, it is not enough, so the array capacity = capacity / factory; in short, the capacity is 3/4 of the array capacity .

  • loadFactor is only used when calculating the initial array size , and then the expansion is fixed at 3/4
  • The expansion problem when the tree threshold is exceeded, if the capacity is already 64, directly tree, otherwise do 3 rounds of expansion on the basis of the original capacity

the difference

①从底层结构上
1.7 The underlying data structure of ConcurrentHashMap is Segment (large array) + HashEntry (small array) + linked list
, while the underlying data structure of 1.8 ConcurrentHashMap is array + linked list or red-black tree , and the addition of elements to the linked list is different from the head insertion method of 1.7, but the tail Interpolation

②从初始化的时机上
Compared with 1.7, it is lazy initialization . 1.7 is hungry-style initialization. After initialization, the array and the zero element of the array have been created. In 1.8, the underlying array structure will be created when the element is put for the first time, which is lazy-style initialization.

③从扩容时机上
1.7 is expansion when the capacity exceeds the expansion factor
, and 1.8 is expansion when the array is full 3/4 (or expansion factor * array capacity)


9. Understanding of ThreadLocal

  • ThreadLocal can realize the thread isolation of [resource objects], let each thread use its own [resource objects], and avoid thread safety problems caused by contention
  • ThreadLocal also implements thread isolation between threads andWithin a thread (as long as it is the same thread, the same variable can be obtained in multiple methods)Resource Sharing

principle

Each thread has a collection of ThreadLocalMap type to store resource objects

  • Calling the set method is to use ThreadLocal itself as the key and the resource object as the value, and put it into the ThreadLocalMap collection of the current thread
  • Calling the get method is to use ThreadLocal itself as the key to find the associated resource value in the current thread
  • Calling the remove method is to use ThreadLocal itself as the key to remove the resource value associated with the current thread

The resources between threads are isolated, the key (ThreadLocal) can be the same, but the associated resource pools may be different

insert image description here

The hash value of the ThreadLocalMap
key is uniformly allocated; the initial capacity is 16; the number of elements is 2/3 (expansion factor), and the array is doubled in size. When the index value is the same, the zipper method of the linked list is no longer used, but the open search Address method (from this index, find the next free position as the index position).


10. Why should the key in ThreadLocalMap be set as a weak reference

weak reference key

The key in ThreadLocalMap is designed as a weak reference for the following reasons

  • Thread may need to run for a long time (such as threads in the thread pool), if the key is no longer used, the memory it occupies needs to be released when the memory is insufficient (GC)

memory release time

①Passive GC release key

  • Only the memory of the key is released, and the memory associated with the value will not be released

② Lazy and passive release of value

  • When getting the key, if it is found to be a null key, then release its value memory; (When ThreadLocalMap goes to get the value according to the key, it finds that the key does not exist, and will put a null key; this is different from other maps)
  • When setting the key, heuristic scanning will be used to clear the value memory of the adjacent null key. The number of heuristics is related to the number of elements and whether a null key is found

③ Actively remove to release key and value

  • The key and value memory will be released at the same time, and the value memory of the adjacent null key will also be cleared
  • It is recommended to use it because it is generally used as a static variable when using ThreadLocal(i.e. strong reference), so it is impossible to passively rely on GC recovery
    insert image description here
    , so both methods ① and ② are not available, it is better to actively recycle

Guess you like

Origin blog.csdn.net/giveupgivedown/article/details/129078029