[High Concurrency Series] 1. Must-know concepts of Java concurrent programming

Some concepts that must be known in Java high concurrency programming~

1. Concurrency and parallelism

Concurrency

        Alternate multiple tasks on a single or multi-core CPU.

Parallelism

        A multi-core CPU handles multiple tasks simultaneously. Note that there is no parallelism for single-core CPUs.

the difference  

        The important difference between the concepts of concurrency and parallelism lies in "a period of time" and "at the same time".

        Concurrency focuses on the alternate execution of multiple tasks, doing multiple things at the same time for a period of time, such as on weekend mornings, coaxing the baby for a while, playing with the mobile phone for a while, the baby makes a fuss, and plays with the mobile phone when the baby is quiet..., so repeatedly (headache ~).

        Parallelism refers to the execution of multiple tasks at the same time. For example, I hold a pen in each hand, draw a square with my left hand, and draw a circle with my right hand (Xiuer~).

2. Synchronous and asynchronous

The two are used to describe a method call.

Synchronous Synchronous

        That is, the calling method starts, and once it is called, it must wait for the method to complete and return before continuing the subsequent operations.

        For example, if you go to a bank ATM to withdraw money, you have to wait until the ATM has finished spitting out the money and you get the money and withdraw the card before you can leave.

Asynchronous Asynchronous

        It's more like a message passing. You don't need to care about the specific execution process of the method. Once triggered, the result will be returned immediately, and the caller can continue the subsequent operations.

        For example, if you want to withdraw money today and the amount is large, you can directly call or make an appointment with the bank to tell the bank how much cash you want to withdraw. During this time, the bank will prepare the money for you, and this preparation process has nothing to do with you. Then you just need to Just pick it up at the scheduled time. For you, you just triggered an asynchronous action or passed a message.

3. Process and thread

concept

Process: The smallest unit for resource allocation by the operating system, where resources include: CPU, memory space, disk IO, etc. Multiple threads in the same process share all system resources in the process, and processes are independent of each other.

Thread: The smallest unit of CPU scheduling, which must exist depending on the process.

the difference

  • Definition: A process is the running process of an entity in which a program runs, and is an independent unit for resource allocation and scheduling by the system; a thread is the smallest scheduling unit for process running and execution.
  • Liveness: the process is not active (it is just a thread container); the thread is active and can be created and destroyed at any time.
  • System overhead: Process creation, cancellation, and switching are expensive, and resources need to be reallocated and recovered. Compared with the process, the thread only saves less register content, has low overhead, and executes code in the address space of the process.
  • Owning Assets: A conduct is the basic unit of resource ownership. Compared with the process, the thread basically does not own resources, but it will occupy the CPU.
  • Scheduling: A process is only the basic unit of resource allocation. Thread is the basic unit of independent scheduling and dispatching.
  • Security: The processes are relatively independent and will not affect each other. Threads share resources under the same process and can communicate and influence each other.

4. Critical section

        A critical section is used to represent a common resource or shared data that can be used by multiple threads, but only one thread can use it at a time.

         Once the critical section resource is occupied, other threads must wait if they want to use this resource.

5. Blocking and non-blocking

        Blocking and non-blocking are usually used to describe the impact between multiple threads.

Blocking Blocking

        If a thread occupies a common resource and does not release the lock on it, other threads can only wait for it to release the lock if they want to continue executing. Waiting will cause the thread to hang, which will cause blocking at this time.

Non-blocking Non-Blocking

        That is, there is no blocking, threads can run freely, no common resources are locked, and they do not block each other.

6. Thread safety

Understanding of Java thread safety:

        When multiple threads access a Java object concurrently, no matter how the system schedules these threads, and no matter how these threads will alternately operate, this object can show consistent and correct behavior, so the operation on this object is thread-safe of. If the object exhibits inconsistent, erroneous behavior, operations on the object are not thread-safe, and a thread safety problem has occurred.

7. Java high-concurrency programming

        After Java5, advanced concurrency features were introduced. Most of the features are in the java.util.concurrent package, which is specially used for multi-threaded programming, making full use of the functions of modern multi-processor and multi-core systems to write large-scale concurrent applications. . It mainly includes atomic weight, concurrent collection, synchronizer, reentrant lock, and provides strong support for the construction of thread pool.

Significance and advantages

  1. Make full use of CPU resources;

  2. Speed ​​up the response time to users;

  3. Make the code modular, asynchronous, and simple.

 Issues to be aware of

  1. Security between threads;

  2. The infinite loop process between threads;

  3. Too many threads will exhaust server resources and cause a crash.

8. Three major problems in concurrent programming

atomicity

Definition: It means that the operation of a thread cannot be interrupted by other threads, and only one thread operates on a variable at the same time.

        In the case of multithreading, the execution result of each thread is not interfered by other threads. For example, multiple threads concurrently call the same shared member variable n++ 100 times. If the initial value of n is 0, the final value of n should be 100, so they do not interfere with each other, which is atomicity.

        In fact, n++ is not an atomic operation, and AtomicInteger can be used to ensure its atomicity.

visibility

Definition: It refers to whether a thread modifies the value of a shared variable, and whether other threads can see the modified value of the shared variable.

        Each thread has its own working memory. The thread first reads the value of the shared variable from the main memory to the working memory to form a copy. Memory is a process. When the main memory has not been flushed back, it is invisible to other threads at this time, so the value read by other threads from the main memory is the old value before modification.

        Visibility problems will arise in CPU cache optimization, hardware optimization, instruction rearrangement, and JVM compiler optimization.

orderliness

Definition: The sequence of program execution is executed in the sequence of codes.

        All operations are ordered if observed within this thread; all operations are unordered if observed in another thread. The first half of the sentence refers to "serial semantics within a thread", and the second half of the sentence refers to the phenomenon of "instruction reordering" and "main memory synchronization delay in working memory".

        The Java volatile keyword itself contains the semantics of prohibiting instruction reordering, while synchronized is obtained by the rule "only one thread is allowed to lock a variable at the same time", which determines that holding the same Two synchronized blocks of a lock can only be entered serially.

9. Java built-in lock and state

describe

        Before Java6, all Java built-in locks are heavyweight locks, and heavyweight locks will cause the CPU to frequently switch between user mode and core mode, which is costly and inefficient. In order to reduce the performance consumption caused by acquiring and releasing locks, Java6 introduces the implementation of biased locks and lightweight locks. Therefore, Java's built-in lock has a total of four states: no lock state, biased lock state, lightweight lock state, and heavyweight lock state. These states will gradually escalate with competition. Built-in locks can be upgraded but not downgraded, which means that after a biased lock is upgraded to a lightweight lock, it cannot be downgraded to a biased lock. This strategy can improve the efficiency of acquiring and releasing locks.

no lock status

        When a Java object is first created, there is no thread to compete for it, indicating that the object is in a lock-free state. At this time, the biased lock flag is 0, and the lock state is 01.

Bias lock state

        Biased lock means that a piece of synchronization code has been accessed by the same thread, then the thread will automatically acquire the lock, thereby reducing the cost of acquiring the lock. If the built-in lock is in a biased state, when there is a thread competing for the lock, the biased lock is used first, indicating that the built-in lock prefers this thread. When this thread wants to execute the synchronization code associated with the lock, it does not need to do any checking and switching. Biased locks are very efficient when the competition is not fierce, because the Mark Word in the biased lock state will record the thread ID that the built-in lock prefers, and the built-in lock will regard the thread as its acquaintance.

lightweight lock state

        When two threads start to compete for this lock object, the situation changes. It is no longer a biased (exclusive) lock. The lock will be upgraded to a lightweight lock. Two threads compete fairly. Which thread occupies the lock object first? The Mark Word of the lock object points to the lock record in the stack frame of which thread.

        When the lock is in a biased lock and another thread attempts to preempt it, the biased lock will be upgraded to a lightweight lock. The thread attempting to preempt will try to acquire the lock in the form of spin, and will not block the lock-grabbing thread, so as to improve performance.

        The spin principle is very simple. If the thread holding the lock can release the lock resource in a short time, then those threads waiting for the competing lock do not need to switch between the kernel state and the user state to enter the blocking and suspending state. You only need to wait (spin), and the lock can be acquired immediately after the thread holding the lock releases the lock, thus avoiding the consumption of user thread and kernel switching.

        However, thread spin needs to consume CPU. If the lock cannot be acquired, the thread cannot always occupy the CPU to spin for useless work, so it is necessary to set a maximum spin waiting time. JVM's choice of spin cycle, Java6 introduced adaptive spin lock, adaptive spin lock means that the spin time is not fixed, but by the previous spin time on the same lock and the lock time depends on the status of the owner. If the thread spins successfully, the number of spins will be more next time, and if the spin fails, the number of spins will be reduced.

        If the execution time of the thread holding the lock exceeds the maximum spin waiting time and the lock is not released, other threads competing for the lock will still not be able to acquire the lock within the maximum waiting time, and the spin will not continue forever. When the contention thread stops spinning and enters the blocking state, the lock expands into a heavyweight lock.

Heavyweight Lock Status

        Heavyweight locks will block other applied threads and reduce performance. Heavyweight locks are also called synchronization locks. This lock object Mark Word changes again and points to a monitor object that registers and manages queued threads in the form of a collection.

Summarize

When using Java's built-in lock, there is no need to explicitly preempt and release the monitor of the synchronization object through Java code. These tasks are done by the underlying JVM, and any Java object can be used as a built-in lock, so Java's object lock It is very convenient to use. However, the built-in lock function of Java is relatively single, and does not have some more advanced lock functions, such as time-limited lock grabbing, interruptible lock grabbing, and multiple waiting queues. In addition to these functional issues, there are performance issues with Java object locking. In the case of slightly fierce competition, Java object locks will expand into heavyweight locks, and the thread blocking and wake-up operations of heavyweight locks require the process to switch back and forth between kernel mode and user mode, resulting in very low performance. Therefore, it is urgent to provide a new lock to improve the performance of locks in intense contention scenarios.

10. JUC explicit lock

        Different from Java's built-in lock, JUC explicit lock is a very flexible lock implemented in pure Java language. The use of this lock is very flexible, and it can perform unconditional, pollable, timed, and interruptible locks. Acquire and release operations. Since the JUC lock locking and unlocking methods are explicitly performed through the Java API, it is also called an explicit lock.

        Java5 introduced the Lock interface, which is a Java code-level lock. To distinguish it from Java object locks, the Lock interface is called an explicit lock interface, and its object instance is called an explicit lock object. It will be introduced in detail later.

11. Exclusive lock and shared lock

        The lock operation is performed before accessing the shared resource, and the unlock operation is performed after the access is completed. According to "whether it is allowed to be held by multiple threads at the same time", locks can be divided into shared locks and exclusive locks.

        Exclusive locks are also called exclusive locks, mutex locks, and exclusive locks, which mean that a lock can only be held by one thread at a time. After a thread locks, any other thread that tries to lock it again will be blocked until the thread holding the lock unlocks it. In layman's terms, shared resources can only be accessed by one thread at a time, and the rest of the threads are blocked and waiting.

        If it is a fair exclusive lock, if more than one thread is blocking and waiting when the thread holding the lock is unlocked, then the thread that grabs the lock first will be awakened and become ready to perform the locking operation, and other threads will still block and wait . Both the Synchronized built-in lock and the ReentrantLock explicit lock in Java are exclusive locks.

        Shared locks are locks that allow multiple threads to hold at the same time. Of course, the thread that acquires the shared lock can only read the data in the critical section, and cannot modify the data in the critical section. Shared locks in JUC include Semaphore (semaphore), read lock in ReadLock (read-write lock), CountDownLatch, and so on.

12. Pessimistic lock and optimistic lock

        An exclusive lock is actually a pessimistic lock, and Java's synchronized is a pessimistic lock. Pessimistic locking ensures exclusive access to critical sections regardless of which thread holds the lock. Although the logic of pessimistic locking is very simple, there are many problems.

        Pessimistic locking always assumes that the worst will happen, and every time a thread reads data, it also locks. In this way, other threads will be blocked when reading data until it gets the lock. Traditional relational databases use many pessimistic locks, such as row locks, table locks, read locks, and write locks.

The pessimistic locking mechanism has the following problems:

(1) Under multi-thread competition, adding and releasing locks will cause more context switching and scheduling delays, causing performance problems.

(2) After a thread holds the lock, it will cause all other threads that preempt the lock to hang.

(3) If a high-priority thread waits for a low-priority thread to release the lock, the priority of the thread will be inverted, causing performance risks.

        An effective way to solve the above problems of pessimistic locking is to use optimistic locking instead of pessimistic locking. Similarly, data updates with version numbers in database operations and atomic classes in JUC packages all use optimistic locking to improve performance.

13. AQS abstract synchronizer

        In a scene with intense contention, using a lightweight lock based on CAS spins will result in vicious CAS empty spins that waste a lot of CPU resources. There are two solutions to this problem: decentralized operation hotspots and peak clipping using queues. The JUC concurrent package uses the queue peak-shaving solution to solve the performance problem of CAS, and provides a peak-shaving base class based on a two-way queue-the abstract base class AbstractQueuedSynchronizer (abstract synchronizer class, referred to as AQS).

        AQS is a basic class provided by JUC for building locks and synchronization containers. Many classes in the JUC package are built based on AQS, such as ReentrantLock, Semaphore, CountDownLatch, ReentrantReadWriteLock, FutureTask, etc. AQS takes care of a lot of the details of design when implementing synchronous containers.

        AQS queue internally maintains a FIFO doubly linked list. The characteristic of this structure is that each data structure has two pointers, pointing to the direct predecessor node and the direct successor node respectively. Therefore, the doubly linked list can easily access the predecessor node and the successor node from any node. Each node is actually encapsulated by a thread. When the thread fails to compete for the lock, it will be encapsulated into a node and added to the AQS queue; when the thread that acquires the lock releases the lock, a blocked thread will be awakened from the queue.

Finally~

There are many must-know concepts involved in Java high-concurrency programming. Here, 13 more important concepts are summarized and sorted out, and only a simple concept description is made, and it does not involve code examples, underlying implementation principles, etc.

[Without accumulating silicon, there is no way to reach a thousand miles, let's make progress together~]

Guess you like

Origin blog.csdn.net/qq_29119581/article/details/129271987