Analysis of 40 high-frequency Java concurrent programming interview questions

1. What is the use of multithreading?

1) Take advantage of multi-core CPU

With the advancement of industry, current notebooks, desktops and even commercial application servers are at least dual-core, and 4-core, 8-core or even 16-core are not uncommon. If it is a single-threaded program, then on a dual-core CPU 50% was wasted, and 75% was wasted on a 4-core CPU. The so-called "multithreading" on a single-core CPU is fake multithreading. The processor can only process a piece of logic at the same time, but the threads switch faster, and it looks like multiple threads are running "simultaneously". Multi-threading on a multi-core CPU is the real multi-threading. It allows your multi-segment logic to work at the same time. Multi-threading can truly take advantage of the multi-core CPU and achieve the purpose of making full use of the CPU.

2) Prevent blocking

From the perspective of program efficiency, a single-core CPU not only does not give full play to the advantages of multithreading, but it will switch the thread context due to running multithreading on a single-core CPU, which reduces the overall efficiency of the program. But for single-core CPU, we still have to use multi-threading, just to prevent blocking. Imagine that if a single-core CPU uses a single thread, then as long as the thread is blocked, for example, reading a certain data remotely, the peer has not returned and has not set a timeout, then your entire program will be before the data is returned. Stopped running. Multi-threading can prevent this problem. Multiple threads are running at the same time, even if the code execution of one thread is blocked for reading data, it will not affect the execution of other tasks.

3) Easy to model

This is another advantage that is not so obvious. Suppose there is a big task A, single-threaded programming, then there are a lot of considerations, and it is troublesome to build the entire program model. But if you decompose this large task A into several small tasks, task B, task C, and task D, build program models separately, and run these tasks separately through multi-threading, it is much simpler.

2. The way to create threads

A more common problem, there are generally two types:

1) Inherit the Thread class

2) Implement the Runnable interface

As for which is better, it goes without saying that the latter is better, because the way to implement interfaces is more flexible than the way of inheriting classes, and it can also reduce the coupling between programs. Interface-oriented programming is also the core of the six principles of design patterns.

3. The difference between start() method and run() method

Only when the start() method is called, will it show the characteristics of multithreading, and the code in the run() method of different threads will be executed alternately. If you just call the run() method, the code is executed synchronously, and you must wait for the code in the run() method of one thread to finish executing before another thread can execute the code in the run() method.

4. The difference between Runnable interface and Callable interface

There is a bit of a deep question, and I also see the breadth of knowledge of a Java programmer.

The return value of the run() method in the Runnable interface is void. What it does is purely to execute the code in the run() method; the call() method in the Callable interface has a return value and is a generic type , And Future, FutureTask can be used to obtain the results of asynchronous execution.

This is actually a very useful feature, because multi-threading is more difficult and more complicated than single-threaded. An important reason is that multi-threading is full of unknowns. Has a thread executed? How long has a thread been executed? Has the data we expect when a thread executes has been assigned? It is impossible to know that all we can do is wait for the completion of this multi-threaded task. Callable+Future/FutureTask can obtain the results of multi-threaded operation, and it is really useful to cancel the task of the thread when the waiting time is too long and the required data is not obtained.

5. The difference between CyclicBarrier and CountDownLatch

Two seemingly similar classes, both under java.util.concurrent, can be used to indicate that the code runs to a certain point. The difference between the two is:

1) After a thread of CyclicBarrier runs to a certain point, the thread stops running until all threads have reached this point, all threads are re-run; CountDownLatch is not, after a thread runs to a certain point , Just give a certain value -1, the thread continues to run.

2) CyclicBarrier can only evoke one task, and CountDownLatch can evoke multiple tasks.

  1. CyclicBarrier can be reused, CountDownLatch cannot be reused, and the CountDownLatch with a count value of 0 can no longer be used.

6, the role of the volatile keyword

A very important issue is that every Java programmer who learns and applies multithreading must master it. The prerequisite for understanding the role of the volatile keyword is to understand the Java memory model. The Java memory model is not discussed here. You can refer to point 31. The volatile keyword has two main functions:

1) Multithreading mainly revolves around the two characteristics of visibility and atomicity. The variable modified by the volatile keyword ensures its visibility between multiple threads, that is, every time a volatile variable is read, it must be the latest data.

2) The underlying execution of the code is not as simple as the high-level language we have seen-the Java program. Its execution is Java code -> bytecode -> execute the corresponding C/C++ code according to the bytecode -> C/ The C++ code is compiled into assembly language -> to interact with the hardware circuit. In reality, the JVM may reorder the instructions in order to obtain better performance, and some unexpected problems may occur under multi-threading. Using volatile will reorder the prohibited semantics, which of course reduces the code execution efficiency to a certain extent.

From a practical point of view, an important role of volatile is to combine with CAS to ensure atomicity. For details, please refer to the classes under the java.util.concurrent.atomic package, such as AtomicInteger.

7. What is thread safety

It's another theoretical question. There are many different answers. I personally think the best explanation: If your code is executed in multiple threads and executed in a single thread, you will always get the same result. , Then your code is thread-safe.

This issue is worth mentioning, that is, there are several levels of thread safety:

1) Immutable

Like String, Integer, and Long, they are all final types. No thread can change their values. They must be changed unless one is newly created. Therefore, these immutable objects can be directly used in a multithreaded environment without any synchronization means. use

2) Absolute thread safety

Regardless of the runtime environment, the caller does not need additional synchronization measures. To do this usually requires a lot of extra costs. Java calls itself a thread-safe class. In fact, most of them are not thread-safe. However, there are absolutely thread-safe classes in Java, such as CopyOnWriteArrayList and CopyOnWriteArraySet.

3) Relative thread safety

Relative thread safety is what we call thread safety in the usual sense. Like Vector, the add and remove methods are all atomic operations and will not be interrupted, but only for this. If a thread is traversing a Vector , There is a thread in the add this Vector at the same time, 99% of the cases will appear ConcurrentModificationException, which is the fail-fast mechanism.

4) Thread is not safe

There is nothing to say about this. ArrayList, LinkedList, HashMap, etc. are all thread-unsafe classes.

8. How to get the thread dump file in Java

For problems such as infinite loops, deadlocks, blocking, slow page openings, etc., thread dump is the best way to solve the problems. The so-called thread dump is the thread stack. There are two steps to obtain the thread stack:

1) To get the pid of the thread, you can use the jps command, or in the Linux environment, you can use ps -ef | grep java

2) To print the thread stack, you can use the jstack pid command, and in the Linux environment, you can also use kill -3 pid

Another point is that the Thread class provides a getStackTrace() method that can also be used to obtain the thread stack. This is an instance method, so this method is bound to a specific thread instance, and each time you get the stack of a specific thread currently running.

9. What happens if a thread has a runtime exception

If the exception is not caught, the thread stops executing. Another important point is: if this thread holds the monitor of a certain object, then the object monitor will be released immediately

10. How to share data between two threads

It is enough to share objects between threads, and then invoke and wait through wait/notify/notifyAll, await/signal/signalAll, for example, the blocking queue BlockingQueue is designed for sharing data between threads

11. What is the difference between sleep method and wait method

This question is often asked. Both the sleep method and the wait method can be used to give up the CPU for a certain amount of time. The difference is that if the thread holds the monitor of an object, the sleep method will not give up the monitor of this object, and the wait method will give up this. Object monitor

12. What is the role of the producer consumer model

This question is very theoretical, but very important:

1) Improve the operating efficiency of the entire system by balancing the production capacity of the producer and the consumption capacity of the consumer. This is the most important role of the producer-consumer model

2) Decoupling, which is an incidental effect of the producer-consumer model. Decoupling means that there are fewer connections between producers and consumers, and the fewer connections, the more they can develop independently without receiving mutual constraints.

13, what is the use of ThreadLocal

Simply put, ThreadLocal is a way of changing space for time. In each Thread, a ThreadLocal.ThreadLocalMap implemented by the open address method is maintained. The data is isolated and the data is not shared. Naturally, there is no thread safety problem.

14. Why wait() method and notify()/notifyAll() method should be called in synchronized block

This is mandatory by the JDK. Both the wait() method and the notify()/notifyAll() method must obtain the object lock before calling

15. What is the difference between wait() method and notify()/notifyAll() method when giving up the object monitor

The difference between the wait() method and the notify()/notifyAll() method when abandoning the object monitor is: the wait() method releases the object monitor immediately, and the notify()/notifyAll() method waits for the remaining code of the thread to finish executing Will give up the object monitor.

16. Why use thread pool

Avoid frequent creation and destruction of threads, and achieve the reuse of thread objects. In addition, the thread pool can also be used to flexibly control the number of concurrent projects.

17. How to detect whether a thread holds an object monitor

I also saw a multi-threaded interview question on the Internet to know that there is a way to determine whether a thread holds an object monitor: the Thread class provides a holdsLock (Object obj) method, if and only if the monitor of the object obj is It will return true when held by a thread. Note that this is a static method, which means "a thread" refers to the current thread.

18. The difference between synchronized and ReentrantLock

Synchronized is the same keyword as if, else, for, while, and ReentrantLock is a class, which is the essential difference between the two. Since ReentrantLock is a class, it provides more and more flexible features than synchronized. It can be inherited, can have methods, and can have a variety of class variables. ReentrantLock is more extensible than synchronized in several aspects:

(1) ReentrantLock can set the waiting time for acquiring the lock, so as to avoid deadlock

(2) ReentrantLock can obtain various lock information

(3) ReentrantLock can flexibly implement multiple notifications

In addition, the lock mechanism of the two is actually different. The bottom layer of ReentrantLock calls the Unsafe park method to lock, and the synchronized operation should be the mark word in the object header. I am not sure about this.

19. What is the concurrency of ConcurrentHashMap

The concurrency of ConcurrentHashMap is the size of the segment. The default is 16, which means that up to 16 threads can operate ConcurrentHashMap at the same time. This is also the biggest advantage of ConcurrentHashMap to Hashtable. In any case, Hashtable can have two threads to obtain Hashtable at the same time. Data?

20, what is ReadWriteLock

First of all, to be clear, it is not that ReentrantLock is bad, but ReentrantLock has some limitations. If you use ReentrantLock, it may itself be to prevent data inconsistency caused by thread A writing data and thread B reading data, but in this way, if thread C is reading data, thread D is also reading data, reading data will not change the data, there is no need Locking, but still locking, reducing the performance of the program.

Because of this, the ReadWriteLock was born. ReadWriteLock is a read-write lock interface. ReentrantReadWriteLock is a specific implementation of the ReadWriteLock interface, which realizes the separation of read and write. The read lock is shared, and the write lock is exclusive. There will be no mutual exclusion between read and write. Write and read, and write and write are mutually exclusive, which improves the performance of reading and writing.

21. What is FutureTask

This is actually mentioned before, FutureTask represents an asynchronous operation task. A specific implementation class of Callable can be passed into FutureTask, which can wait to obtain the result of this asynchronous operation task, determine whether it has been completed, and cancel the task. Of course, since FutureTask is also an implementation class of the Runnable interface, FutureTask can also be placed in the thread pool.

22. How to find which thread uses the longest CPU in the Linux environment

This is a more practical problem, and I think this kind of problem is quite meaningful. You can do this:

(1) Get the pid, jps or ps -ef | grep java of the project, as I have mentioned before

(2) top -H -p pid, the order cannot be changed

In this way, you can print out the current project and the percentage of CPU time that each thread occupies. Note that what is typed here is the LWP, which is the thread number of the native thread of the operating system. My laptop does not deploy the Java project in the Linux environment, so there is no way to take a screenshot to demonstrate. If the company uses the Linux environment to deploy the project, you can try a bit.

Use "top -H -p pid"+"jps pid" to easily find the thread stack of a thread that occupies a high CPU, so as to locate the cause of high CPU occupancy. Generally, improper code operations lead to an infinite loop.

Lastly, the LWP typed by "top -H -p pid" is decimal, and the local thread number typed by "jps pid" is hexadecimal. After conversion, you can locate the thread that takes up high CPU. The current thread stack is up.

23, Java programming write a program that will cause deadlock

The first time I saw this topic, I thought it was a very good question. Many people know what a deadlock is: Thread A and Thread B wait for each other's locks, causing the program to loop endlessly. Of course, it is limited to this. I don’t know how to write a deadlock program. In this case, I don’t understand what a deadlock is. If I understand a theory, it’s over. In practice, we encounter deadlock problems. Basically it is invisible.

To truly understand what a deadlock is, this problem is not difficult, just a few steps:

1) The two threads hold two Object objects: lock1 and lock2. These two locks are used as locks for synchronization code blocks;

2) The synchronization code block in the run() method of thread 1 first acquires the object lock of lock1, Thread.sleep(xxx), it does not take much time, 50 milliseconds is almost the same, and then acquires the object lock of lock2. The main purpose of this is to prevent thread 1 from acquiring the object locks of lock1 and lock2 at once.

3) Thread 2 run) (The synchronization code block in the method first acquires the object lock of lock2, and then acquires the object lock of lock1. Of course, the object lock of lock1 has been held by the thread 1 lock, and thread 2 must be waiting for thread 1. Release the object lock of lock1

In this way, thread 1 "sleeps" after sleeping, thread 2 has acquired the object lock of lock2, and thread 1 tries to acquire the object lock of lock2 at this time, and it is blocked. At this time, a deadlock is formed. The code is not written, it takes up a lot of space. Java Multithreading 7: Deadlock This article contains the code implementation of the above steps.

24, how to wake up a blocked thread

If the thread is blocked by calling wait(), sleep() or join(), you can interrupt the thread and wake it up by throwing InterruptedException; if the thread encounters IO blocking, there is nothing you can do, because IO is the operating system Realized, there is no way for Java code to directly touch the operating system.

25. How does immutable objects help multithreading

A problem mentioned earlier is that immutable objects ensure the memory visibility of objects, and the reading of immutable objects does not require additional synchronization means, which improves code execution efficiency.

26. What is multi-threaded context switching

Multi-threaded context switching refers to the process in which CPU control is switched from an already running thread to another thread that is ready and waiting to obtain CPU execution rights.

27. What happens if the thread pool queue is full when you submit a task?

Distinguish here:

1) If you are using an unbounded queue LinkedBlockingQueue, that is, an unbounded queue, it does not matter, continue to add tasks to the blocking queue to wait for execution, because LinkedBlockingQueue can be regarded as an infinite queue, which can store tasks infinitely

2) If you are using a bounded queue such as ArrayBlockingQueue, the task will first be added to the ArrayBlockingQueue. When the ArrayBlockingQueue is full, the number of threads will be increased according to the value of maximumPoolSize. If the number of threads is increased, the number of threads cannot be processed, and the ArrayBlockingQueue continues to be full, then Will use the rejection policy RejectedExecutionHandler to process full tasks, the default is AbortPolicy

28. What is the thread scheduling algorithm used in Java

Preemptive. After a thread runs out of CPU, the operating system will calculate a total priority based on thread priority, thread starvation and other data and allocate the next time slice to a thread for execution.

29, what is the role of Thread.sleep(0)

This question is related to the one above, and I am connected. Because Java uses a preemptive thread scheduling algorithm, it may happen that a thread often obtains control of the CPU. In order to allow some threads with lower priority to also obtain control of the CPU, you can use Thread.sleep( 0) Manually trigger an operation of the operating system to allocate time slices, which is also an operation to balance CPU control

30. What is spin

Many codes in synchronized are just simple codes, and the execution time is very fast. At this time, waiting threads are locked may be a not worthwhile operation, because thread blocking involves switching between user mode and kernel mode. Since the code in synchronized executes very fast, let the thread waiting for the lock not be blocked, but do a busy loop on the boundary of synchronized, which is spin. If you do multiple busy loops and find that the lock has not been obtained, block again, this may be a better strategy

31. What is the Java memory model

The Java memory model defines a specification for multi-threaded access to Java memory. The complete description of the Java memory model is not something that can be stated clearly in a few words here. Let me briefly summarize several parts of the Java memory model:

1) Java memory model divides memory into main memory and working memory. The state of the class, that is, the variables shared between classes, is stored in the main memory. Every time a Java thread uses these variables in the main memory, it will read the variables in the main memory once and let these memories exist. There is a copy in your working memory. When you run your own thread code, you use these variables and manipulate the copy in your working memory. After the thread code is executed, the latest value will be updated to the main memory

2) Several atomic operations are defined to manipulate variables in main memory and working memory

3) Define the rules for the use of volatile variables

4) happens-before, that is, the principle of first occurrence, which defines some rules that operation A must occur before operation B. For example, the code in front of the control flow in the same thread must occur before the code behind the control flow, and a release lock unlock The action must happen before the lock lock action for the same lock and so on. As long as these rules are met, no additional synchronization measures are required. If a piece of code does not meet all happens-before rules, this piece of code must Is thread-unsafe

32. What is CAS

CAS, the full name is Compare and Swap, that is, compare-replace. Suppose there are three operands: the memory value V, the old expected value A, and the value to be modified B. If and only if the expected value A and the memory value V are the same, the memory value will be modified to B and return true, otherwise what Do nothing and return false. Of course, CAS must cooperate with volatile variables, so as to ensure that the variable obtained each time is the latest value in the main memory, otherwise the old expected value A will always be a constant value A for a certain thread, as long as If a CAS operation fails, it will never succeed.

33. What is optimistic lock and pessimistic lock

1) Optimistic lock: Just like its name, it is optimistic about the thread safety issues caused by concurrent operations. Optimistic lock believes that competition does not always occur, so it does not need to hold the lock. Compare-replace these two As an atomic operation, the action attempts to modify the variables in the memory. If it fails, it means that there is a conflict, and there should be corresponding retry logic.

2) Pessimistic lock: still like its name, it is pessimistic about thread safety issues caused by concurrent operations. Pessimistic lock believes that competition will always occur, so every time a resource is operated, it will hold an exclusive Locks are like synchronized, no matter what the situation is, you can operate the resources directly after the lock is on.

34. What is AQS

Briefly talk about AQS, the full name of AQS is AbstractQueuedSychronizer, which should be translated as abstract queue synchronizer.

If the basis of java.util.concurrent is CAS, then AQS is the core of the entire Java concurrent package, and it is used in ReentrantLock, CountDownLatch, Semaphore, etc. AQS actually connects all Entry in the form of a two-way queue. For example, ReentrantLock, all waiting threads are placed in an Entry and connected into a two-way queue. If the previous thread uses ReentrantLock, the two-way queue is actually the first Entry starts to run.

AQS defines all operations on two-way queues, but only opens the tryLock and tryRelease methods for developers to use. Developers can rewrite the tryLock and tryRelease methods according to their own implementation to achieve their own concurrent functions.

35. Thread safety of singleton mode

It's a commonplace question. The first thing to say is that the thread safety of the singleton mode means that an instance of a certain class will only be created once in a multithreaded environment. There are many ways to write singleton mode, let me summarize

1) Hungry Chinese-style singleton pattern writing: thread safety

2) Lazy-style singleton pattern writing: not thread safe

3) The writing of the double check lock singleton mode: thread safety

36. What is the role of Semaphore

Semaphore is a semaphore, and its role is to limit the number of concurrent code blocks. Semaphore has a constructor that can pass in an int integer n, which means that a certain piece of code can only be accessed by at most n threads. If it exceeds n, please wait until a certain thread finishes executing this code block, and the next thread Re-enter. It can be seen that if the int type integer n=1 passed in the Semaphore constructor, it is equivalent to becoming a synchronized.

37. There is clearly only one statement "return count" in the size() method of Hashtable, why do we need to synchronize?

This is my previous confusion, I don’t know if you have thought about this issue. If there are multiple statements in a method, and they are all operating on the same class variable, then no locking in a multithreaded environment will inevitably lead to thread safety issues. This is easy to understand, but the size() method clearly has only one statement , Why do we need to lock?

There are two main reasons for understanding this issue through working and studying slowly:

1) Only one thread can execute the synchronized method of a fixed class at the same time, but for the non-synchronized method of the class, multiple threads can access it at the same time. Therefore, there is a problem. Maybe thread A is executing the put method of Hashtable to add data, thread B can normally call the size() method to read the number of current elements in the Hashtable, and the value read may not be the latest Thread A may have finished adding data, but thread B has already read the size without matching size++, so the size read by thread B must be inaccurate. After adding synchronization to the size() method, it means that the thread B calls the size() method only after the thread A calls the put method, which ensures thread safety.

2) The CPU executes code, but it is not Java code. This is very important and must be remembered. Java code is ultimately translated into machine code for execution, and machine code is the code that can really interact with hardware circuits. Even if you see that there is only one line of Java code, and even if you see that there is only one line of bytecode generated after the Java code is compiled, it does not mean that there is only one operation of this sentence for the bottom layer. One sentence of "return count" is assumed to be translated into three assembly sentences to execute, and one assembly sentence corresponds to its machine code. It is entirely possible that the thread will switch after the first sentence is executed.

38. Thread class construction method and static block are called by which thread

This is a very tricky and cunning question. Remember: the construction method and static block of the thread class are called by the thread where the thread class new is located, while the code in the run method is called by the thread itself.

If the above statement confuses you, let me give you an example. Suppose Thread1 is new in Thread2 and Thread2 is new in the main function. Then:

1) The construction method and static block of Thread2 are called by the main thread, and the run() method of Thread2 is called by Thread2 itself.

2) The construction method and static block of Thread1 are called by Thread2, and the run() method of Thread1 is called by Thread1 itself.

39. Which is the better choice of synchronization method and synchronization block

Synchronous block, which means that the code outside the synchronous block is executed asynchronously, which improves the efficiency of the code more than the entire method of synchronization. Please know a principle: the smaller the scope of synchronization, the better.

With this article, I would like to mention one more point. Although the smaller the synchronization range, the better, there is still an optimization method called lock coarsening in the Java virtual machine, which is to enlarge the synchronization range. This is useful. For example, StringBuffer is a thread-safe class. Naturally, the most commonly used append() method is a synchronization method. When we write code, we will append strings repeatedly, which means repeated locks. ->Unlock, which is detrimental to performance, because it means that the Java virtual machine has to repeatedly switch between kernel mode and user mode on this thread, so the Java virtual machine locks the code of multiple append method calls. The coarse operation extends multiple append operations to the beginning and the end of the append method, and becomes a large synchronization block, which reduces the number of locks -> unlocks, and effectively improves the efficiency of code execution.

40. How to use thread pools for businesses with high concurrency and short task execution time? How do businesses with low concurrency and long task execution time use thread pools? How to use thread pool for business with high concurrency and long business execution time?

This is a question I saw on the concurrent programming website. I put this question on the last one. I hope everyone can see and think about it, because this question is very good, very practical and very professional. Regarding this issue, my personal opinion is:

1) For services with high concurrency and short task execution time, the number of threads in the thread pool can be set to the number of CPU cores + 1, reducing thread context switching

2) Businesses with low concurrency and long task execution time should be distinguished:

a) If the business time is long concentrated on IO operations, that is, IO-intensive tasks, because IO operations do not occupy the CPU, so do not let all the CPUs idle, you can increase the number of threads in the thread pool to let the CPU Handle more business

b) If the business time is long and concentrated on computing operations, that is, computationally intensive tasks, there is no way to do this. Same as (1), set the number of threads in the thread pool to be less, reducing thread context switching

c) High concurrency and long business execution time. The key to solving this type of task is not the thread pool but the overall architecture design. Seeing whether some data in these businesses can be cached is the first step, and adding servers is the second As for the setting of thread pool, please refer to other articles about thread pool. Finally, the problem of long business execution time may also need to be analyzed to see if middleware can be used to split and decouple tasks.

Reference link

Guess you like

Origin blog.csdn.net/qq_40220309/article/details/104657998