What is Java multithreading
Java provides a mechanism for handling multiple tasks concurrently (simultaneously and independently). Multiple threads coexist in the same JVM process, so they share the same memory space. Compared with multiple processes, communication between multiple threads is lighter. In my understanding, Java multithreading is entirely to improve CPU utilization. Java threads have 4 states: New, Runnable, Blocked, and Dead. The key is blocking. Blocking means waiting. The blocking thread does not participate in the time slice allocation of the thread dispatcher, so naturally it will not use the CPU. In a multithreaded environment, those non-blocked threads run and make full use of the CPU.
A summary of 40 questions
1. What is the use of multi-threading?
A question that may seem nonsense to many people: I can just use multi-threading, but what's the use? In my opinion, this answer is even more nonsense. The so-called "knowing what is true" is "knowing what is true", "knowing what is true", "why use" is "knowing what is true". Only by reaching the level of "knowing what is true" can one be said to be able to apply a knowledge point freely. OK, let me tell you what I think about this issue:
(1) Give full play to the advantages of multi-core CPU
With the advancement of industry, today's laptops, desktops and even commercial application servers are all dual-core, and 4-core, 8-core or even 16-core are not uncommon. If it is a single-threaded program, 50% is wasted on the dual-core CPU and 75% is wasted on the 4-core CPU. The so-called "multi-threading" on a single-core CPU is fake multi-threading. The processor will only process a piece of logic at the same time, but the threads switch relatively quickly, which looks like multiple threads are running "at the same time". Multithreading on multi-core CPUs is the real multithreading. It can allow your multi-segment logic to work at the same time and multi-threading. It can truly give full play to the advantages of multi-core CPUs to achieve the goal of making full use of the CPU.
(2) Prevent blockage
From the perspective of program operation efficiency, a single-core CPU will not only not give full play to the advantages of multi-threading, but will instead reduce the overall efficiency of the program because running multi-threading on a single-core CPU will cause thread context switching, which will reduce the overall efficiency of the program. However, we still need to apply multithreading to single-core CPUs to prevent blockage. Just imagine, if a single-core CPU uses a single thread, as long as the thread is blocked, for example, remotely reading a certain data, the peer has not returned for a long time and has not set a timeout time, then your entire program will stop running before the data is returned. Multi-threading can prevent this problem. Multiple threads run at the same time. Even if the code of one thread is blocked from 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 large task A, single-threaded programming, then you need to consider a lot, and it is more troublesome to build the entire program model. However, if you break this big task A into several small tasks, task B, task C, and task D, build a program model separately, and run these tasks separately through multiple threads, it will be much simpler.
2. How to create threads
A more common problem is generally two:
(1) Inherit the Thread class
(2) Implement the Runnable interface
As for which one is better, it goes without saying that the latter is definitely better, because the way to implement interfaces is more flexible than the inheritance class method, and can also reduce the coupling between programs. Interface-oriented programming is also the core of the six major principles of design patterns.
3. The difference between start() method and run() method
Only when the start() method is called will the characteristics of multi-threading be displayed, and the code in the run() method of different threads will be executed alternately. If you just call the run() method, the code will be executed synchronously. You must wait for the code in the run() method of one thread to be executed before another thread can execute the code in its run() method.
4. The difference between Runnable interface and Callable interface
There is a bit of a deep question, and it also shows the breadth of knowledge learned by a Java programmer.
The return value of the run() method in the Runnable interface is void, and what it does is just to execute the code in the run() method; the call() method in the Callable interface has a return value, which is a generic, and can be used to obtain the results of asynchronous execution in conjunction with Future and FutureTask.
This is actually a very useful feature, because a major reason why multithreading is more difficult and more complex than single threading is that multithreading is full of unknowns. Has a certain thread been executed? How long has a thread been executed? Is the data we expect when a thread is executed? It is unaware that all we can do is wait for this multi-threaded task to be executed. Callable+Future/FutureTask can obtain the results of multi-threaded running, and can cancel the task of the thread when the waiting time is too long and the required data is not obtained. It is really useful.
5. The difference between CyclicBarrier and CountDownLatch
Both classes that look a bit similar can be used to indicate that the code runs at a certain point under java.util.concurrent. The difference between the two is:
(1) After a thread of CyclicBarrier runs at a certain point, the thread stops running. Until all threads reach this point, all threads will not run again; CountDownLatch is not. After a thread runs at a certain point, it just gives a certain value -1, and the thread continues to run.
(2) CyclicBarrier can only evoke one task, CountDownLatch can evoke multiple tasks
(3) CyclicBarrier can be reused, CountDownLatch cannot be reused, and the count value is 0 will not be reused, and the CountDownLatch will not be used again
6. The role of volatile keywords
A very important issue is that every Java programmer who learns and applies multi-threading must master it. The prerequisite for understanding the role of the volatile keyword is to understand the Java memory model. I won’t talk about the Java memory model here. You can refer to point 31. There are two main functions of the volatile keyword:
(1) Multithreading mainly revolves around the two characteristics of visibility and atomicity. The variables modified by the volatile keyword ensure their visibility between multiple threads, that is, every time the volatile variable is read, it must be the latest data.
(2) The underlying code execution is not as simple as the high-level language we see - Java programs. Its execution is Java code --> Bytecode --> Execute the corresponding C/C++ code according to the bytecode -> C/C++ code is compiled into assembly language --> interacting with the hardware circuit. In reality, in order to obtain better performance, the JVM may reorder the instructions, and some unexpected problems may arise under multi-threading. Using volatile will prohibit semantic reordering, which of course reduces the efficiency of code execution 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, you can refer to the classes under the java.util.concurrent.atomic package, such as AtomicInteger.
7. What is thread safety
Another theoretical question, there are many answers. I would like to give one that I personally think is the best explanation: if your code can always get the same result when executed under multithreading and single-threading, then your code is thread-safe.
There is something worth mentioning about this problem, that is, there are several levels of thread safety:
(1) Immutable
Like String, Integer, Long, etc., they are all final types. No thread can change their values. If you want to change them, you will not create a new one. Therefore, these immutable objects can be used directly in a multi-threaded environment without any synchronization means.
(2) Absolute thread safety
Regardless of the runtime environment, the caller does not need additional synchronization measures. To do this, you usually have to pay a lot of extra costs. In Java, it is actually not thread-safe classes. However, there are also classes that are absolutely thread-safe, such as CopyOnWriteArrayList and CopyOnWriteArraySet
(3) Relative thread safety
Relative thread safety is what we usually call thread safety. For example, Vector, add and remove methods are atomic operations and will not be interrupted, but it is limited to this. If a thread traversing a certain Vector, a thread is added at the same time, a ConcurrentModificationException will occur in 99% of the cases, which is the fail-fast mechanism.
(4) Threads are not safe
There is nothing to say about this. ArrayList, LinkedList, HashMap, etc. are all non-threaded classes.
8. How to get thread dump file in Java
For problems such as dead loop, deadlock, blocking, slow page opening, etc., hitting thread dump is the best way to solve the problem. The so-called thread dump is the thread stack. There are two steps to obtain the thread stack:
(1) Get the thread's pid, you can use the jps command, and you can also use ps -ef | grep java in Linux environment
(2) Print the thread stack, you can use the jstack pid command, and you can also use kill -3 pid in Linux environment
Also, the Thread class provides a getStackTrace() method that can also be used to get the thread stack. This is an instance method, so this method is bound to a specific thread instance. Each time you get the stack currently running on a specific thread.
9. What happens if a thread has a runtime exception?
If this exception is not caught, the thread will stop executing. Another important point is: if this thread holds a monitor of a certain object, the object monitor will be released immediately
10. How to share data between two threads
Just share objects between threads, and then evoke and wait through wait/notify/notifyAll, await/signal/signalAll. For example, blocking queue BlockingQueue is designed for sharing data between threads.
11. What is the difference between sleep method and wait method
This question is frequently asked, both the sleep method and the wait method can be used to give up the CPU for a certain period 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 the monitor of this object.
12. What is the role of the producer consumer model?
This question is theoretical, but important:
(1) Improve the operating efficiency of the entire system by balancing the production capacity of producers and consumers' consumption capacity, which is the most important role of the producer consumer model.
(2) Decoupling, this is the function accompanied by the producer and consumer model. Decoupling means that there are fewer connections between producers and consumers. The fewer connections, the more they can develop on their own without receiving mutual constraints.
13. What is the use of ThreadLocal
Simply put, ThreadLocal is a practice of exchanging space for time. Each Thread maintains a ThreadLocal.ThreadLocalMap implemented by the open address method to isolate the data and do not share the data, so there will naturally be no thread safety issues.
14. Why do wait() and notify()/notifyAll() methods be called in the synchronization block
This is forced by JDK. The wait() method and notify()/notifyAll() method must first obtain the object's lock before calling
15. What is the difference between wait() method and notify()/notifyAll() method when abandoning the object monitor
The difference between the wait() method and the notify()/notifyAll() method when giving up the object monitor is that the wait() method immediately releases the object monitor, while the notify()/notifyAll() method will wait for the remaining code of the thread to be executed before giving up the object monitor.
16. Why use thread pool
Avoid frequent creation and destruction of threads to achieve reuse of thread objects. In addition, using thread pools can also flexibly control the number of concurrency according to the project.
17. How to detect whether a thread holds an object monitor
I also saw a multithreaded 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, which will return true if and only if the monitor of the object obj is held by a thread. Note that this is a static method, which means that "a certain thread" refers to the current thread.
18. The difference between synchronized and ReentrantLock
synchronized is the same keyword as if, else, for, and 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, which can be inherited, have methods, and have various class variables. The scalability of ReentrantLock than synchronized is reflected in several points:
(1) ReentrantLock can set the waiting time for acquiring the lock, thus avoiding deadlock
(2) ReentrantLock can obtain various lock information
(3) ReentrantLock can flexibly implement multi-channel notification
In addition, the locking mechanisms of the two are actually different. The underlying ReentrantLock calls Unsafe's park method to lock, and the synchronized operation should be the mark word in the object header, I can't be sure of this.
19. What is the concurrency of ConcurrentHashMap
The concurrency of ConcurrentHashMap is the size of segment, which is 16 by default, which means that at most 16 threads can operate ConcurrentHashMap at the same time. This is also the biggest advantage of ConcurrentHashMap on Hashtable. In any case, can Hashtable have two threads at the same time obtain the data in Hashtable?
20. What is ReadWriteLock
First of all, let’s make it clear that it is not that ReentrantLock is not good, it is just that ReentrantLock is limited at some times. If ReentrantLock is used, it may be to prevent data inconsistencies caused by thread A writing data and thread B reading data. However, in this way, if thread C reading data and thread D is also reading data, reading data will not change the data. There is no need to lock it, but it still locks it, which reduces the performance of the program.
Because of this, the read-write lock ReadWriteLock was born. ReadWriteLock is a read-write lock interface. ReentrantReadWriteLock is a concrete implementation of the ReadWriteLock interface, which realizes the separation of read and write. Read locks are shared and write locks are exclusive. Read and read and read will not be mutually exclusive. Only read and write, write and read, write and write will be mutually exclusive, improving the performance of read and write.
21. What is FutureTask
This is actually mentioned earlier. FutureTask represents an asynchronous operation task. A specific implementation class of Callable can be passed into FutureTask, which can wait for the result of this asynchronous operation to obtain, 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 Linux environment
This is a more practical problem, and I think this problem is quite meaningful. You can do this:
(1) Get the project's pid, jps or ps -ef | grep java, which has been mentioned before
(2) top -H -p pid, the order cannot be changed
This will print out the percentage of CPU time that the current project takes for each thread. Note that the one here is LWP, which is the thread number of the operating system native thread. My notebook mountain does not deploy Java projects in the Linux environment, so there is no way to take screenshots and demonstrations. If the company is deploying a project using a Linux environment, you can try it.
Using "top -H -p pid" + "jps pid" can easily find a thread stack that occupies a high CPU, thereby positioning the reason for occupancy of high CPU, which is generally due to improper code operations that lead to a dead loop.
Finally, let me mention that the LWP played with "top -H -p pid" is decimal, and the local thread number played with "jps pid" is hexadecimal. After conversion, you can locate the current thread stack that occupies a high CPU.
23. Java programming write a program that will cause deadlock
I saw this question for the first time and thought it was a very good question. Many people know what deadlock is like: Thread A and Thread B are waiting for each other's locks to cause an infinite dead loop to continue the program. Of course, it's only limited to this. If you ask how to write a deadlock program, you won't know. To put it bluntly, you don't understand what a deadlock is. If you understand a theory, you'll be done. You can basically not see the problem of deadlock in practice.
To truly understand what a deadlock is, this question is not difficult, there are a few steps:
(1) Two threads hold two Object objects: lock1 and lock2 respectively. These two locks serve as locks for synchronous code blocks;
(2) In the run() method of thread 1, the synchronization code block first obtains the object lock of lock1, Thread.sleep(xxx), the time does not take too much, 50 milliseconds are almost the same, and then obtains the object lock of lock2. This is mainly done to prevent thread 1 from continuously obtaining object locks of two objects: lock1 and lock2.
(3) Run of thread 2) (In the method, the synchronization code block first obtains the object lock2, and then acquires the object lock1. Of course, the object lock1 is already held by thread 1 lock, and thread 2 must wait for thread 1 to release the object lock1 lock1.
In this way, after thread 1 "sleeps" and thread 2 has acquired the object lock2. Thread 1 tries to acquire the object lock2 at this time, and is blocked. At this time, a deadlock is formed. I won’t write the code anymore, 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 blocking thread
If the thread blocks due to calling wait(), sleep() or join() methods, it can interrupt the thread and wake it up by throwing an InterruptedException; if the thread encounters IO blockage, it is powerless because IO is implemented by the operating system, and Java code cannot directly contact the operating system.
25. What help does immutable objects help multithreading
A problem mentioned above is that immutable objects ensure the memory visibility of objects, and there is no need for additional synchronization to read immutable objects, which improves code execution efficiency.
26. What is multi-threaded context switching
Multithreaded context switching refers to the process of switching CPU control from one running thread to another thread ready and waiting for the CPU execution rights to be obtained.
27. If the thread pool queue is full when you submit a task, what will happen at this time
If you are using LinkedBlockingQueue, that is, unbounded queues, it doesn't matter. Continue to add tasks to the blocking queue and wait for execution, because LinkedBlockingQueue can be almost considered an infinite queue and can store tasks infinitely; if you are using a bounded queue, for example, ArrayBlockingQueue, the task will be added to the ArrayBlockingQueue first. If the ArrayBlockingQueue is full, the RejectedExecutionHandler will use the rejection policy to handle the full tasks, and the default is AbortPolicy.
28. What is the thread scheduling algorithm used in Java?
Preemptive style. After a thread uses up CPU, the operating system will calculate a total priority based on data such as thread priority, thread hunger, etc. and allocate the next time slice to a thread for execution.
29. What is the function of Thread.sleep(0)
This question is related to the above question, and I'm all together. Since Java uses preemptive thread scheduling algorithm, it may occur that a thread often obtains CPU control. In order to allow some threads with relatively low priority to obtain CPU control, Thread.sleep(0) can be used to manually trigger the operating system allocate time slices, which is also an operation to balance CPU control.
30. What is spin
Many synchronized codes are just some very simple code, and the execution time is very fast. Locking the threads waiting at this time may be a not worthwhile operation, because thread blocking involves user-state and kernel-state switching. Since the code in synchronized executes very fast, you might as well let the thread waiting for the lock not be blocked, but instead do busy loops at the boundary of synchronized. This is spin. If you have done multiple busy loops and find that the lock has not been obtained, and then block it, this may be a better strategy.
31. What is Java memory model
The Java memory model defines a specification for multi-threading access to Java memory. The Java memory model needs to be fully explained, but I can't explain it clearly in a few sentences here. Let me summarize it briefly.
Several parts of the Java memory model:
(1) The Java memory model divides memory into main memory and working memory. The state of the class, that is, the variables shared between classes, are 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 and let them exist in its own working memory. When running its own thread code, it uses these variables and operates the one in its own 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 operate variables in main memory and working memory
(3) Define the rules for using volatile variables
(4) happens-before, that is, the principle of first occurrence, defines some rules in which operation A must occur first in operation B. For example, the code in front of the control flow in the same thread must occur first in the code behind the control flow, the action of releasing the lock unlock must occur first in the action of locking the same lock, etc. As long as these rules are met, no additional synchronization measures are required. If a certain piece of code does not comply with all happens-before rules, then this piece of code must be thread-not safe.
32. What is CAS
CAS, full name Compare and Set, is Compare-Set. Suppose there are three operands: memory value V, the old expected value A, the value B to be modified. If and only if the expected value A and memory value V are the same, the memory value will be modified to B and returned true, otherwise nothing will be done and false will be returned. Of course, CAS must cooperate with the volatile variable, 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 value A that will not change for a thread. As long as a certain 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 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, and will compare - set these two actions as an atomic operation to try to modify variables in memory. If it fails, it means a conflict occurs, and then there should be corresponding retry logic.
(2) Pessimistic lock: Just like its name, it is pessimistic about thread safety issues caused by concurrent operations. Pessimistic locks believe that competition will always occur. Therefore, every time a resource is operated, it will hold an exclusive lock, just like synchronized, regardless of whether it is locked directly, and the resource will be operated.
34. What is AQS
Let’s briefly talk about AQS. The full name of AQS is AbstractQueuedSychronizer. It should be an abstract queue synchronizer when translated.
If the basis of java.util.concurrent is CAS, then AQS is the core of the entire Java concurrency package, and ReentrantLock, CountDownLatch, Semaphore, etc. all use it. AQS actually connects all Entry in the form of a bidirectional queue. For example, ReentrantLock. All waiting threads are placed in an Entry and connected into a bidirectional queue. If the previous thread uses ReentrantLock, then the first Entry of the bidirectional queue actually starts running.
AQS defines all operations on bidirectional 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 implement their own concurrency functions.
35. Thread safety of singleton mode
A cliché issue, the first thing to say is that the thread safety of singleton pattern means that: instances of a certain class will only be created once in a multi-threaded environment. There are many ways to write singleton pattern, let me summarize:
(1) Writing the singleton pattern of Hungry Man: thread safety
(2) Writing lazy singleton pattern: non-thread-safe
(3) Write the singleton mode of double check lock: thread safety
36. What is the function of Semaphore
Semaphore is a semaphore, and its function is to limit the number of concurrency in a certain code block. Semaphore has a constructor that can pass int integer n, indicating that only n threads can access a certain piece of code. If n is exceeded, please wait until a thread completes the code block and enter the next thread. From this we can see that if the int integer n=1 passed in the Semaphore constructor is equivalent to becoming a synchronized.
37. There is only one statement "return count" in the size() method of Hashtable, so why do you still need to synchronize?
This is a confusion I had before, and I wonder if you have thought about this question. If there are multiple statements in a method and they are all operating the same class variable, then if you do not add locks in a multi-threaded environment, it will inevitably cause thread safety issues. This is easy to understand, but the size() method clearly has only one statement, so why do you still need to add locks?
Regarding this issue, I have understood it through working and studying slowly, and there are two main reasons:
(1) Only one thread can execute the synchronization method of the fixed class at the same time, but for the asynchronous method of the class, multiple threads can access it at the same time. So, there is a problem. Maybe thread A is adding data when executing the put method of Hashtable, and thread B can call the size() method normally to read the number of current elements in the Hashtable. The value read may not be the latest. Maybe thread A has added the data, but without size++, thread B has already read the size. So for thread B, the size read must be inaccurate. After adding synchronization to the size() method, it means that thread B calls the size() method only after 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 you must remember it. Java code is eventually translated into assembly code for execution, and assembly code is the code that can truly interact with hardware circuits. Even if you see that there is only one line of Java code, and even if you see that the Java code is compiled, there is only one line of bytecode generated, it does not mean that for the underlying layer, there is only one operation for this statement. The sentence "return count" is assuming that it is translated into three assembly statements to execute, and it is entirely possible that the thread will switch after the first sentence is executed.
38. Which thread is the constructor of the thread class and the static block called by which thread
This is a very tricky and cunning question. Please remember: the constructor and static block of the thread class are called by the thread where the new thread class is located, and the code in the run method is called by the thread itself.
If the above statement confuses you, then let me give you an example, suppose that new Thread1 is in Thread2 and new Thread2 is in main function, then:
(1) Thread2's constructor and static block are called by the main thread, and Thread2's run() method is called by Thread2 itself
(2) Thread1's constructor and static block are called by Thread2, and Thread1's run() method is called by Thread1 itself
39. Which is the better choice between synchronization method and synchronization block?
Synchronize blocks, which means that the code outside the synchronization block is executed asynchronously, which improves the efficiency of the code more efficient than synchronizing the entire method. Please know one principle: the less synchronization range
The better.
With this article, I would like to mention that although the smaller the synchronization range, the better, there is still an optimization method called lock coarseness in Java virtual machines, which is to increase 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 repeatedly append the string, which means repeatedly locking -> unlocking, which is not good for performance, because it means that the Java virtual machine has to repeatedly switch between kernel state and user state on this thread. Therefore, the Java virtual machine performs a lock-coarse operation on the code called by multiple append method, extending multiple append operations to the head and tail of the append method, and turning it into a large synchronization block. This reduces the number of lock--> unlocking times, effectively improving the efficiency of code execution.
40. How to use thread pools for businesses with high concurrency and short task execution time? How to use thread pools for businesses with low concurrency and long task execution time? How to use thread pools for businesses with high concurrency and long service execution time?
This is a question I saw on the concurrent programming website. I put this question last and 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) High concurrency and short task execution time, the number of thread pool threads can be set to CPU core number +1 to reduce the switching of thread context
(2) Businesses with low concurrency and long task execution time should be viewed separately:
a) If the business time is concentrated on IO operation, that is, IO-intensive tasks, because IO operation does not occupy CPU, so don’t let all CPUs be idle. You can increase the number of threads in the thread pool and let the CPU handle more services
b) If the business time is concentrated on computing operations, that is, computing-intensive tasks, there is nothing we can do about this. Just like (1), the number of threads in the thread pool should be set to reduce the switching of thread context.
(3) 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. It is the first step to see if some data in these services can be cached, and adding a server is the second step. As for the settings of the thread pool, the settings reference (2). 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.
Types of Java thread blocking (Blocked) :
Call the sleep function and enter sleep state, Thread.sleep(1000) or TimeUnit.SECONDS.sleep(1), sleep will not release the lock.
Waiting for a certain event is divided into two types: (wait, notify, notifyAll), (await, signal, signalAll), and will be introduced in detail later. wait and await release the lock and must be called in the environment where the lock was acquired.
In the waiting lock, synchronized and lock environment, the lock has been taken away by other threads, waiting to acquire the lock.
IO blocking (blocked), such as network waiting, file opening, console reading. System.in.read().