On-line interviewer, JUC interview topic enhancement

1. AQS high frequency problem

1.1 What is AQS? 

AQS is the basic class of a large number of tools under JUC. Many tools are implemented based on AQS, such as lock, , CountDownLatch, Semaphorethread pool, etc. all use AQS.

There is a core attribute state in AQS, as well as a doubly linked list and a one-way linked list. Among them, the state is modified based on volatile, and then modified based on CAS, which can guarantee the three characteristics of atomicity, visibility and order. The one-way linked list is the waiting pool in the internal class benchmark. When the lock is held by the thread, the await method is executed, and the thread is encapsulated as a Node object, which is thrown into the Condition one-way linked list, waiting to be woken up ConditionObject. synchronizedIf the thread wakes up, throw the Node in the Condition to the AQS doubly linked list and wait for the lock to be acquired.

1.2 Why does AQS traverse from back to front when waking up a thread?

  After the execution of the thread holding the resource is completed, one needs to be taken out of the AQS doubly linked list. If the next node of the head is canceled

If the next node of the head node is the first to be woken up when waking up the thread, if the next node of the head is canceled, the node loss problem will occur.

As shown in the figure below, when a new Node is added to the linked list, there are 3 steps. When the third step is not completed, if the node that needs to be woken up cannot be found starting from the head.

 

1.3 Why does AQS use a two-way linked list (why not use a one-way linked list)?

Because in AQS, there is an operation to cancel nodes, if you use a doubly linked list, only two steps are required

  • The next pointer of the prev node needs to point to the next node.

  • The prev pointer of the next node needs to point to the prev node.

But if it is a one-way linked list, you need to traverse the entire one-way linked list to complete the above operations. It is a waste of resources.

1.4 Why does AQS have a virtual head node

Each Node will have some state, this state is not only for itself, but also for subsequent nodes

  • 1: The current node is canceled.

  • 0: Default state, nothing happens.

  • -1: The successor node of the current node, pending.

  • -2: Represents that the current node is in the Condition queue (await suspends the thread)

  • -3: It means that it is currently a shared lock. When waking up, subsequent nodes still need to be woken up.

However, a node cannot save the state of the current node and the state of the successor node at the same time. There is a sentinel node, which is more convenient to operate.

1.5 The underlying implementation principle of ReentrantLock

ReentrantLockIt is implemented based on AQS.

  • When the thread is based on ReentrantLocklocking, it is necessary to modify the state attribute based on CAS. If it can be changed from 0 to 1, it means that the lock resource is acquired successfully.

  • If CAS fails, add to AQS's doubly linked list and queue (threads may be suspended), waiting to acquire the lock.

  • If the thread holding the lock executes the await method of the condition, the thread will be encapsulated as Node and added to the one-way linked list of the condition, waiting to be awakened and re-competing for the lock resource

1.6 The difference between fair lock and unfair lock of ReentrantLock

The implementation of the lock method and the tryAcquire method in fair locks and unfair locks is a bit different, and everything else is the same

unfair lock

  • lock: Directly try to change the state from 0 to 1. If it succeeds, take the lock and go directly. If it fails, execute tryAcquire.

  • tryAcquire: If no thread currently holds the lock resource, try again to change the state from 0 to 1. If it succeeds, take the lock and go directly.

fair lock

  • lock: Execute tryAcquire directly.

  • tryAcquire: If there is no thread currently holding the lock resource, check first to see if there is a queue. If there is no queue, try to change the state from 0 to 1 directly. If there is a queue and the first place, directly try to change the state from 0 to 1.

If the lock is not obtained, the follow-up logic of the fair lock and the unfair lock is the same, and it is added to the AQS doubly linked list for queuing.

1.7 How ReentrantReadWriteLock implements the read-write lock

If an operation writes less and reads more and uses a mutex, the performance is too low, because there is no concurrency problem in reading and reading. Read-write locks can solve this problem.

ReentrantReadWriteLockIt is also a read-write lock based on AQS, but the lock resource is identified by state. How to identify two lock information based on an int, there is a write lock and a read lock, how to do it?

An int occupies 32 bits. When the write lock acquires the lock, the value of the lower 16 bits of the state is modified based on the CAS. When the read lock acquires the lock, the value of the upper 16 bits of the state is modified based on the CAS.

The re-entry of the write lock is directly identified based on the lower 16 of the state, because the write lock is mutually exclusive. The reentry of read locks cannot be identified based on the upper 16 bits of the state, because read locks are shared and can be held by multiple threads at the same time. Therefore, the reentry of the read lock is represented by ThreadLocal, and at the same time, the high 16 of the state will be appended.

2. High frequency problem of blocking queue

2.1 Tell me about the blocking queue you are familiar with?

  • ArrayBlockingQueue: The bottom layer is implemented based on arrays, remember to set the boundaries when new.

  • LinkedBlockingQueue: The bottom layer is implemented based on a linked list, which can be considered as an unbounded queue, but the length can be set.

  • PriorityBlockingQueue: The bottom layer is a binary heap implemented based on an array, which can be considered as an unbounded queue, because the array will expand.

ArrayBlockingQueue, LinkedBlockingQueueare ThreadPoolExecutorthe two most commonly used blocking queues for thread pools.

2.2 What is spurious wakeup?

False wakeup is reflected in the source code of the blocking queue.

 

 

For example, when consumer 1 consumes data, it will first determine whether there are elements in the queue. If the number of elements is 0, consumer 1 will await and hang up. The position where the element is judged to be 0 here will cause a problem if the if loop is used.

  • If the producer adds a piece of data, it will wake up consumer 1 and go to lock the resource.

  • At this time, if consumer 2 comes and grabs the lock resource and takes away the data, when consumer 1 gets the lock resource again, it cannot get any elements from the queue, and a false wakeup problem occurs.

The solution is to set the position of judging the number of elements to while judging.

3. High-frequency problem of thread pool

3.1 7 parameters of the thread pool

Number of core threads, maximum number of threads, maximum idle time, time unit, blocking queue, thread factory, rejection policy

3.2 What is the state of the thread pool and how is it recorded?

The thread pool has 5 states: RUNINING, SHUTDOWN, STOP, TIDYING,TERMINATED

 

The state of the thread pool is recorded in the ctl attribute. The essence is int type

3.3 Common rejection strategies for thread pools

AbortPolicy: discard task and throw exception (default)

CallerRunsPolicy: current thread execution

 

DiscardPolicy: Discard the task and do not directly

DiscardOldestPolicy: Discard the oldest task in the waiting queue and execute the current task

 

In general, when the built-in thread pool cannot satisfy the business, you can customize a rejection policy of the thread pool and implement the following interface.

3.4 Thread Pool Execution Process

The core thread is not built after new, it is a lazy loading mechanism, and the core thread will be built only after adding tasks

 

3.5 Why does the thread pool add non-core threads of empty tasks

Avoid the thread pool with tasks in the queue, but no worker threads to process them.

When the number of core threads is 0, the task will go to the blocking queue after it comes in, but there are no worker threads. At this time, the non-core thread of the empty task can process the task.

3.6 When there is no task, what are the worker threads in the thread pool doing?

  • If it is a core thread, by default, the take method will be executed at the position of the blocking queue until the task is obtained.

  • If it is a non-core thread, by default, the poll method will be executed at the position of the blocking queue, waiting for the maximum idle time, if there is no task, delete the thread, if there is work, then work normally.

3.7 What problems will be caused by the exception of the worker thread?

The worker thread that throws an exception first will not affect other worker threads.

  • If the task is executed by the execute method, the worker thread will throw an exception.

  • If the task is executed by the submit method futureTask, the worker thread will capture and save the exception in it FutureTask, and futureTaskthe exception information can be obtained based on get.

  • Finally the thread ends.

3.8 What is the purpose of worker threads inheriting AQS?

The essence of a worker thread is the Worker object. Inheriting AQS has a relationship with shutdownand shutdownNow.

  • If it is shutdown, it will interrupt the idle worker thread, and judge whether the worker thread can be interrupted based on the value of the state in the AQS implemented by the Worker. If the state of the worker thread is 0, it means it is idle and can be interrupted. If it is 1, it means it is working.

  • If yes shutdownNow, forcefully interrupt all worker threads directly

3.9 How to set the core parameters?

The purpose of the thread pool is to reduce resource consumption caused by frequent creation/destruction of threads, give full play to CPU resources, and improve the performance of the entire system. The thread pool reference methods of different businesses are also different.

  • If it is a CPU-intensive task, it is generally the number of core threads of the number of CPU cores + 1. This is enough to give full play to the CPU performance.

  • If it is an IO-intensive task, because the degree of IO is different, some are 1s, some are 1ms, and some are 1 minute, so when an IO-intensive task is processed by a thread pool, it is necessary to observe the occupancy of CPU resources through pressure testing. , to determine the number of core threads. Generally, it is enough to play the CPU performance to 70~80.

Therefore, the parameter settings of the thread pool need to go through pressure testing and multiple adjustments to get specific.
 

Four, CountDownLatch, Semaphore high frequency problem

4.1 What is CountDownLatch? What's the use? How is the bottom layer realized?

CountDownLatchThe essence is actually a counter. When multi-threaded parallel processing of business, you need to wait for other threads to finish processing before doing subsequent operations such as merging, you can use CountDownLatchcounting, and wait until the other threads appear, the main thread will be woken up. The implementation process is as follows:

  • CountDownLatchItself is implemented based on AQS. new CountDownLatch, specify the specific value directly, and this value will be copied to the state attribute.

  • When the child thread finishes processing the task, it executes the countDown method, which directly gives state - 1 internally.

  • When the state is reduced to 0, the thread suspended by await will be awakened.

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch count = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程" + finalI + "执行中");
                count.countDown();
            }).start();
        }

        count.await();

        System.out.println("所有线程都执行完成了");
    }
}

4.2 What is a Semaphore? What's the use? How is the bottom layer realized?

Semaphore is a tool class that can be used for current limiting function. For example, if the current service requires at most 3 threads to work at the same time, set the semaphore to 3. Before submitting each task, you need to obtain a semaphore, and then go to work when you get it, and return the semaphore when you are done. The implementation process is as follows:

  • The semaphore is also implemented based on AQS. When building the semaphore, specify the number of semaphore resources, and this value will be copied to the state attribute.

  • When acquiring the semaphore, execute the acquire method, which is directly given internally state - 1. When the state is 0, the new task will wait because the semaphore cannot be obtained.

  • When the task execution is completed, execute the release method to release the semaphore.

import java.util.concurrent.Semaphore;

public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println("线程" + finalI + "执行中");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }

            }).start();
        }

        new Thread(() -> {
            try {
                long begin = System.currentTimeMillis();
                semaphore.acquire();
                long end = System.currentTimeMillis();
                System.out.println("限流了" + (end - begin) + "ms");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }).start();
    }
}

4.3 When the main thread ends, will the program stop?

  • If the main thread ends, but there are still user threads executing, it will not end!

  • If the main thread ends, the rest are daemon threads, end!

5. High-frequency problems of CopyOnWriteArrayList

5.1 How does CopyOnWriteArrayList ensure thread safety? Are there any downsides?

CopyOnWriteArrayListWhen writing data, atomicity is guaranteed based on ReentrantLock. When writing data, a copy will be copied and written. After the writing is successful, it will be written to CopyOnWriteArrayListthe array in , so as to ensure that there will be no data inconsistency when reading data.

The disadvantage is that if the amount of data is relatively large, a copy needs to be copied every time the data is written, which occupies too much space. If the amount of data is relatively large, it is not recommended to use it CopyOnWriteArrayList.

It is suitable for scenarios where atomicity is required for write operations, concurrency is guaranteed for read operations, and the amount of data is small.

6. High-frequency problems of ConcurrentHashMap (JDK1.8)

6.1 Why is HashMap not thread-safe?

  • There are rings in JDK1.7 (during expansion).

  • Data is overwritten when data is added concurrently, and data may be lost.

  • When recording the number of elements and the number of times HashMap is written, the records are not accurate.

  • Data migration, expansion, and data loss may also occur.

6.2 How does ConcurrentHashMap ensure thread safety?

  • tail plug

  • When writing into an array, security is guaranteed based on CAS; when inserting into a linked list or red-black tree, security is guaranteed based on synchronized.

  • Here ConcurrentHashMapis the technology implemented by LongAdder, and the bottom layer is still CAS.

  • ConcurrentHashMapWhen expanding capacity, CAS-based guarantees that there will be no concurrency issues in data migration.

6.3 After the ConcurrentHashMap is built, is the array created? If not, how can I guarantee the thread safety of initializing the array?

ConcurrentHashMapIt is a lazy loading mechanism, and most of the framework components are lazy loading~

It is based on CAS to ensure the safety of the initialization thread. This not only involves CAS to modify the sizeCtl variable to control the atomicity of the thread initialization data, but also uses DCL. The outer layer judges that the array is not initialized, and the sizeCtl is modified based on CAS in the middle. Make an array uninitialized judgment.

 

6.4 Why is the load factor 0.75, and why does the linked list turn into a red-black tree when the length reaches 8?

The load factor of 0.75 can be explained in two ways. Why not 0.5, why not 1?

0.5: If the load factor is 0.5, adding half of the data will start expanding

  • Advantages: less hash collisions and high query efficiency.

  • Disadvantages: Expansion is too frequent, and the space utilization rate is low.

1: If the load factor is 1, the data is added to the length of the array to start expanding

  • Advantages: Infrequent capacity expansion, good space utilization.

  • Disadvantages: Hash conflicts will be particularly frequent, and data will be hung on the linked list, which will affect the query efficiency, and even the linked list will be too long to generate a red-black tree, which will affect the efficiency of writing.

0.75 can be said to be a middle choice, taking into account both aspects.

Then there is the Poisson distribution. When the load factor is 0.75, according to the Poisson distribution, the probability of the length of the linked list reaching 8 is very low. The logo in the source code is that the probability of generating a red-black tree is extremely low 0.00000006. Although ConcurrentHashMapthe red-black tree is introduced, the maintenance cost of the red-black tree is higher for writing. If you can use it, you don’t need it. The comments of the HashMap source code also describe that you should avoid the red-black tree as much as possible.

6.5 The scene where the put operation is too frequent will cause the blockage of put during the expansion period?

Normally it will not cause blockage.

  • If it is found that there is no data at the current index position during the put operation, the data will be dropped to the old array normally.

  • If during the put operation, it is found that the current location data has been migrated to the new array, and it cannot be inserted normally at this time, to help expand the capacity, quickly end the expansion operation, and re-select the index position query

6.6 When will ConcurrentHashMap be expanded, and what is the expansion process?

  • ConcurrentHashMapIf the number of elements in the load factor reaches the threshold value of load factor calculation, then directly expand the capacity

  • When the putAll method is called to query a large amount of data, it may also cause a direct expansion operation. If the inserted data is greater than the threshold for the next expansion, the large amount of data will be directly expanded and then inserted.

  • When the length of the array is less than 64 and the length of the linked list is greater than or equal to 8, expansion will be triggered

 

6.7 How to realize the counter of ConcurrentHashMap?

This is implemented based on the mechanism of LongAdder, but it does not directly use the reference of LongAdder, but writes a code with a similarity of more than 80% according to the principle of LongAdder, and uses it directly.

LongAdder uses CAS to add to ensure atomicity, and secondly based on segment locks to ensure concurrency.

6.8 Will the read operation of ConcurrentHashMap be blocked?

No matter where it is checked, it will not be blocked.

  • Query array : Check whether the element is in the array, and return it directly.

  • Query the linked list : in the linked list next, next can be queried.

  • When expanding : If the current index position is -1, it means that all the data at the current position has been migrated to the new array, and you can directly go to the new array to query, no matter whether the expansion is completed or not.

  • Query the red-black tree : When converting the red-black tree, there is not only a red-black tree, but also a doubly linked list. At this time, the doubly linked list will be queried to prevent the reading thread from being blocked.

Guess you like

Origin blog.csdn.net/zhangjiaming_zjm/article/details/130523959