Thread correlation

1. What’s the difference between sleep and wait?

1.1 sleep

  1. Sleep is a thread method
  2. The sleep method only frees the CPU, not the synchronous resource lock
  3. Sleep to time automatically restores
  4. Sleep can be called anywhere

1.2 wait

  1. Wait applies to objects and is a method of Object
  2. The WAIT method releases a synchronous resource lock while releasing CPU resources, and the thread is in the wait pool for that object
  3. Wait requires a call to notify/notifyAll to wake up the thread from the wait pool. When a thread invokes the notifyAll() or notify() methods of an object, the awakened thread enters the lock pool of the object, and the threads compete for the lock. That is, after notify is called, only one thread enters the lock pool from the wait pool, and notifyAll moves all threads in the wait pool to the lock pool, waiting for lock contention.
  4. The wait() method can only be used in synchronized methods or synchronized blocks;

2. A deadlock

Two threads each hold a different lock and then try to acquire the other’s lock, causing both to wait indefinitely. This is called a deadlock.

Public void add(int m) {synchronized(lockA) {this.value += m; Synchronized (lockB) {this. Another += m; } public void dec(int m) {synchronized(lockB) {this.another -= m; Synchronized (lockA) {this.value -= m; } // release lockA lock} // release lockB lock}Copy the code

For the above code, thread 1 and thread 2 execute the add() and dec() methods respectively:

  • Thread 1: Accessadd()To obtainlockA;
  • Thread 2: Accessdec()To obtainlockB.

Then:

  • Thread 1: Ready to getlockBFailing, waiting;
  • Thread 2: Ready to getlockAFailed waiting.

At this point, the two threads enter a deadlock state, there is no solution, can only kill the JVM

3. Java memory structure

  • Heap: Thread sharing. All object instances and arrays are allocated on the heap. The objects that the collector mainly manages.
  • Method Area: shared by threads. Store class information, constants, static variables, and just-in-time compiler compiled code.
  • JVM Stack: Thread private. Store local variable table, operation stack, dynamic link, method exit, object pointer.
  • Native Method Stack: thread private. Native methods used by virtual machines. When Java uses interface services written in C or C ++, the code runs in this area.
  • Program Counter Register: Thread private. Some articles also translate as PC Register, the same thing. It can be thought of as a line number indicator of the bytecode being executed by the current thread. Points to the next instruction to execute.

The JVM memory model is a comparison of the various JDK versions

4. Java memory model

The Java Memory Model (JMM) is a mechanism and specification that conforms to the specification of the Memory Model, shielding the access differences of various hardware and operating systems, and ensuring that the Java program can get the same effect on the access of Memory on various platforms. The goal is to solve atomicity, visibility (cache consistency), and orderliness problems due to multiple threads communicating through shared memory.

4.1 atomic

An operation in which the CPU may not pause and then schedule without interrupting, completing, or not executing.

Thread is the basic unit of CPU scheduling. The CPU has the concept of time slice and will schedule threads according to different scheduling algorithms. So in a multithreaded scenario, atomicity problems occur. When a thread performs a read rewrite operation, it is required to abandon the CPU and wait for rescheduling after the time slice runs out. In this case, read rewriting is not an atomic operation. There is a problem with atomicity.

  • Synchronized can be used in Java to ensure that operations within methods and code blocks are atomic.

4.2 Visibility (cache consistency)

When multiple threads access a variable and one thread changes the value of the variable, the other threads can immediately see the changed value.

In a multi-core CPU, multi-threaded scenario, each core has at least one L1 cache. If multiple threads access a shared memory in a process and execute on different cores, each core keeps a buffer of shared memory in its own CAEHE. Since multiple cores can be parallel, it is possible for multiple threads to write to their caches at the same time, and the data in their caches may be different. Adding caches between CPU and main memory can lead to cache consistency issues in multi-threaded scenarios, that is, in a multi-core CPU, each core’s own cache may have different contents for the same data.

  • Volatile Ensures visibility by blocking memory and prohibiting instruction reordering. (a), to read operation of volatile, will increase before reading operation a load barrier instruction (b), to the volatile for write operation, will add a store after the write operation barrier instruction memory barriers: A set of instructions in a processor used to restrict the order of memory operations (instructions cannot be rearranged to place subsequent instructions in front of the memory barrier)
  • Synchronized Before a variable can be unlocked, it must be synchronized back to main memory (store, write)
  • Final variables are immutable from initialization

4.3 order

That is, the order in which the program is executed is the order in which the code is executed. In addition to the introduction of time slices, due to processor optimization and instruction rearrangement, the CPU may also execute input code out of order, such as load->add->save may be optimized to load->save-> Add. That’s the problem of order.

The Java memory model stipulates that all variables are stored in the main memory, and each thread has its own working memory. The working memory of the thread stores a copy of the main memory of the variables used in the thread. All operations on variables must be carried out in the working memory of the thread, instead of reading and writing the main memory directly. Different threads cannot directly access variables in each other’s working memory, and the transfer of variables between threads requires data synchronization between their own working memory and main memory.

The JMM is used to synchronize data between working memory and main memory. It specifies how and when to synchronize data.

  • The volatile keyword disallows instruction reordering.
  • The synchronized keyword ensures that only one thread can operate at a time.

Please don’t tell me about the stack method section when you ask about the Java memory model again… (Feels good)

Thread pool correlation

1. ThreadPoolExecutor – The root of thread pools

1. Constructor

publicThreadPoolExecutor(
        int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler)
Copy the code

CorePoolSize: indicates the maximum number of core threads in the thread pool maximumPoolSize: indicates the maximum number of threads in the thread pool keepAliveTime: indicates the keepAliveTime unit: indicates the keepAliveTime unit workQueue: ThreadFactory: specifies the factory that created the thread. Handler: represents the policy that the thread pool adopts when the workQueue is full and the number of threads in the pool reaches maximumPoolSize to refuse to add new tasks

There are three types of workqueues

  1. Commit SynchronousQueue directly
  2. Unbounded queue LinkedBlockingQueue
  3. Bounded queue ArrayBlockingQueue

For details, see: Thread pool bottom queue details

For details, see Thread pool ThreadPoolExecutor and Executors parameter description and source code analysis

2. Policies created by threads in the thread pool

After a task is submitted to the thread pool: (1) If there are no free threads to execute the task and the number of threads currently running is less than corePoolSize, a new thread is added to execute the task. (2) If there are no idle threads to execute the task and the current number of threads is equal to corePoolSize and the blocking queue is not full, the task will be queued without adding new threads. (3) If there are no free threads to execute the task, the blocking queue is full and the number of threads in the pool is smaller than maximumPoolSize, a new thread is created to execute the task. (4) If there are no free threads to execute the task, the blocking queue is full and the number of threads in the pool is equal to maximumPoolSize, the new task is rejected according to the policy specified by the handler in the constructor. (5) If there are free threads, but the number of threads in the current thread pool is less than corePoolSize, add a new thread to perform the task. Thread pools are lazy threads by default, but they are threads that want to have a core thread count quickly.

For details, see Thread pool ThreadPoolExecutor and Executors parameter description and source code analysis

Why is the thread pool designed to create new threads when the queue is full + the core thread count is full? Instead of creating a new thread when the backlog reaches a certain threshold?

2. Factory method for thread pool in Executors

  1. NewCachedThreadPool: Create a cacheable thread pool. If the length of the thread pool exceeds the processing requirement, you can recycle idle threads. If not, create a new thread. (Maximum number of concurrent threads cannot be controlled) used to execute a large number of short, small tasks concurrently, or on a lightly loaded server.
  2. NewFixedThreadPool: Creates a fixed size thread pool that controls the maximum number of concurrent threads, and the excess threads wait in the queue. For servers with heavy load, the current number of threads needs to be limited in order to rationally utilize resources.
  3. NewScheduledThreadPool: Creates a scheduled thread pool that supports scheduled and periodic task execution. Used in scenarios where multiple background threads are required to perform periodic tasks and the number of threads needs to be limited.
  4. NewSingleThreadExecutor: Creates a single-threaded thread pool that uses only one worker thread to execute tasks, ensuring that all tasks are executed in the specified order (FIFO, LIFO, priority). Used for scenarios where tasks are executed sequentially, each task must be executed sequentially and not concurrently.

For details, see: Executors Create four common thread pools

The lock

1. What are the types of locks? What’s the difference?

Reference: The unspoken Java “lock” thing

1.1 Optimistic locks vs. Pessimistic locks

Optimistic locks do not add locks because they assume that no other thread will modify the data while they are using it. Optimistic locking is implemented in Java using lock-free programming, most commonly CAS spin implementation.

Pessimistic locks assume that data must be modified by other threads when they are using data. Therefore, they lock data before acquiring data to ensure that data cannot be modified by other threads. In Java, the implementation classes for the synchronized keyword and Lock are pessimistic locks.

1.2 Spinlocks VS adaptive spinlocks

A spin lock is one in which the thread attempting to acquire the lock does not block immediately, but instead attempts to acquire the lock in a circular manner. This has the advantage of reducing the cost of thread context switching, but the disadvantage is that the loop consumes CPU. The implementation principle is also CAS. In AtomicInteger, the source code calling unsafe for the increment operation, the do-while loop is a spin operation. If changing the value fails, the loop will execute the spin operation until the change succeeds.

Adaptive spin lock spin time (times) is no longer fixed, but is determined by the previous spin time on the same lock and the state of the lock owner. If the spin wait has just successfully acquired the lock on the same lock object, and the thread holding the lock is running, the virtual machine will assume that the spin wait is likely to succeed again, and it will allow the spin wait to last a relatively long time. If spin is rarely successfully acquired for a lock, it is possible to omit the spin process and block the thread directly in future attempts to acquire the lock, avoiding wasting processor resources.

1.3 No-lock VS biased lock VS lightweight lock VS heavyweight lock

These four types of locks refer to states of lock, specifically synchronized. See the synchronized implementation below for details

1.4 Fair Locks VS Unfair Locks

A fair lock means that multiple threads acquire locks in the order in which they apply for locks. The thread directly enters the queue to queue, and the first thread in the queue can obtain the lock. The advantage of a fair lock is that the thread waiting for the lock does not starve. The disadvantage is that the overall throughput efficiency is lower than that of the unfair lock. All threads except the first thread in the waiting queue will block, and the cost of CPU waking up the blocked thread is higher than that of the unfair lock.

An unfair lock is a process in which multiple threads attempt to acquire the lock directly and wait at the end of the queue if they fail to acquire the lock. However, if the lock is available at this time, the thread can directly acquire the lock without blocking, so an unfair lock may occur in which the line that applied for the lock later obtains the lock first. The advantage of unfair locking is that it reduces the overhead of invoking threads, and the overall throughput is high, because threads have a chance to acquire the lock without blocking and the CPU does not have to wake up all threads. The downside is that threads in a waiting queue might starve to death or wait too long to acquire locks. The only difference is whether a thread can cut the queue before it enters the queue. ReentrantLock can be a fair lock or an unfair lock. The default unfair lock synchronized is a unfair lock

1.5 Reentrant locks VS non-reentrant locks

A reentrant lock, also known as a recursive lock, means that if the same thread acquires the lock from the outer method, the inner method of the same thread automatically acquires the lock (provided that the lock object is the same object or class). The lock is not blocked because it has been acquired before and has not been released. Java ReentrantLock and synchronized are both reentrantlocks. One advantage of ReentrantLock is that it can avoid deadlocks to some extent.

1.6 Exclusive lock VS Shared Lock

An exclusive lock, also called an exclusive lock, means that the lock can only be held by one thread at a time. If thread T holds an exclusive lock on data A, no other thread can hold any type of lock on data A. A thread that acquires an exclusive lock can both read and modify data. The implementation classes for synchronized in the JDK and Lock in JUC are mutex.

A shared lock means that the lock can be held by multiple threads. If thread T adds A shared lock to data A, other threads can only add A shared lock to data A, not an exclusive lock. The thread that acquires the shared lock can only read the data, not modify it.

Exclusive lock and shared lock is also achieved through AQS, through the implementation of different methods, to achieve exclusive or shared. Through ReentrantLock and ReentrantReadWriteLock source code to achieve exclusive lock and shared lock

2. What are the specific implementations of locking in Java?

2.1 synchronized

Unfair lock reentrant lock exclusive lock reference: synchronized

2.1.1 Effects of synchronized

  • atomic
  • visibility
  • order

2.1.2 The use of synchronized

  • Modifier instance method: Lock object to current object (this)
  • Modify static methods: Lock the current class as this.class, which will apply to all object instances of the class
  • Modifies code block: Specifies the lock object and locks the given object/class

2.1.3 Principles of synchronized

Let’s start with two concepts:

  • Java object head

Synchronized is a pessimistic lock that requires a lock on a synchronized resource before operating on it. This lock is stored in the Java object header. Let’s take Hotspot virtual machine as an example. Hotspot’s object header contains two parts of data: Mark Word (Mark field) and Klass Pointer (type Pointer).

Mark Word: Default store object HashCode, generational age, and lock flag bit information. This information is irrelevant to the definition of the object itself, so Mark Word is designed as a non-fixed data structure to store as much data as possible in a very small amount of memory. It reuses its own storage space based on the state of the object, which means that the data stored in Mark Word changes as the lock flag bit changes during runtime.

Klass Point: a pointer to the object’s class metadata that the virtual machine uses to determine which class the object is an instance of.

  • Monitor

Monitor can be understood as a synchronization tool or a synchronization mechanism and is often described as an object. Each Java object has an invisible lock, called an internal lock or Monitor lock.

Monitor is a thread-private data structure, and each thread has a list of available Monitor Records, as well as a global list of available records. Each locked object is associated with a Monitor, and an Owner field in the monitor stores the unique identity of the thread that owns the lock, indicating that the lock is occupied by the thread.

Monitorenter and Monitorexit directives, which allow an object to execute, increase or decrease its lock counter by one. Each object is associated with only one monitor at a time, and a monitor can only be acquired by one thread at a time. When an object attempts to acquire ownership of the monitor lock associated with the object, one of three things happens to the Monitorenter instruction:

  • The monitor counter is 0, which means that it has not been acquired yet, so the thread will acquire it immediately and then lock the counter +1. Once +1, another thread will have to wait to acquire it again
  • If the monitor has acquired ownership of the lock and re-entrants the lock, the lock counter accumulates, becoming 2, and continues to accumulate as the lock is re-entrant
  • The lock has already been acquired by another thread, waiting for the lock to be released

Therefore, the reentrant principle of synchronized is realized by increasing or decreasing the value of the monitor counter

2.1.4 Synchronized is a heavyweight lock? What are the states of synchronized?

Jdk6 is no longer a heavyweight lock after optimization. A lock upgrade exists.

Lock expansion direction: no lock → biased lock → lightweight lock → heavyweight lock (this process is irreversible)

unlocked

None Lock No resource is locked. All threads can access and modify the same resource, but only one thread can modify the resource successfully.

The lockless feature is that the modification operation occurs in a loop, and the thread constantly tries to modify the shared resource. If there are no conflicts, the modification succeeds and exits, otherwise the loop continues. If multiple threads modify the same value, one thread will succeed, and the others will retry until the modification succeeds. The CAS principle and application described above is the implementation of lock-free. Lockless can’t completely replace locking, but the performance of locking is very high in some cases.

Biased locking

Biased locking means that a piece of synchronized code is always accessed by a thread, so the thread will automatically acquire the lock, reducing the cost of acquiring the lock.

In most cases, the lock is always acquired multiple times by the same thread, and there is no multithreading competition, so biased locking occurs. The goal is to improve performance when only one thread executes a synchronized code block.

When a thread accesses a block of synchronized code and acquires a lock, the thread ID of the lock bias is stored in Mark Word. Instead of using CAS to lock and unlock a thread when it enters and exits a synchronized block, it checks whether the Mark Word stores a biased lock pointing to the current thread. Biased locks are introduced to minimize unnecessary lightweight lock execution paths without multithreading competition, because the acquisition and release of lightweight locks depend on multiple CAS atomic instructions, while biased locks only need to rely on one CAS atomic instruction when replacing ThreadID.

Biased lock Only when other threads attempt to compete for biased lock, the thread holding biased lock will release the lock, the thread will not actively release biased lock. A partial lock is revoked by waiting for the global safe point (at which no bytecode is being executed), which first suspends the thread with the partial lock to determine whether the lock object is locked. After the biased lock is revoked, the system restores to the no-lock (flag bit is “01”) or lightweight lock (flag bit is “00”) state.

Biased locking is enabled by default in JDK 6 and later JVMS. Biased locking can be turned off by JVM arguments: -xx: -usebiasedLocking =false. After this is turned off, the program enters the lightweight locking state by default.

Lightweight lock

It means that when a biased lock is accessed by another thread, the biased lock will be upgraded to a lightweight lock. Other threads will try to acquire the lock through the form of spin without blocking, thus improving performance.

When the code enters the synchronization block, if the Lock status of the synchronization object is lockless (the Lock flag bit is “01”, whether the bias Lock is “0”), the VIRTUAL machine will first establish a space named Lock Record in the stack frame of the current thread, which is used to store the copy of the current Mark Word of the Lock object. Then copy the Mark Word from the object header into the lock record.

After the copy is successful, the VM attempts to update the Mark Word of the object to a pointer to the Lock Record using the CAS operation, and sets the owner pointer in the Lock Record to the Mark Word of the object.

If the update succeeds, the thread owns the lock on the object, and the object’s Mark Word lock bit is set to 00, indicating that the object is in a lightweight locked state.

If the lightweight lock fails to update, the virtual machine first checks whether the object’s Mark Word refers to the current thread’s stack frame. If it does, the current thread already owns the lock and can proceed directly to the synchronization block. Otherwise, multiple threads compete for the lock.

If there is currently only one waiting thread, the thread waits through spin. But when the spin exceeds a certain number, or when one thread is holding the lock, one is spinning, and a third person is calling, the lightweight lock is upgraded to the heavyweight lock.

Heavyweight lock

When upgrading to a heavyweight lock, the status value of the lock flag changes to “10”, then the pointer to the heavyweight lock is stored in the Mark Word, and all the threads waiting for the lock will enter the blocking state.

2.2 The Lock and already

The Lock interface and its implementation class ReentrantLock have been added in JDK5

  • ReentrantLock

An unfair lock is used by default. Reentrant locks can also be specified using fair locks, as shown by the constructor. NonReentrantLock can be used exclusively for non-reentrantlocks. The shared lock can be ReentrantReadWriteLock

Implementation and variants are too many, please automatic search for details.

2.3 Advantages and disadvantages of Synchronized and Lock

The defect of synchronized

  • Low efficiency: The lock is released only when the code is executed or abnormally finished. You cannot set a timeout when trying to acquire a Lock, and you cannot interrupt a thread that is using the Lock. In contrast, a Lock can interrupt and set a timeout
  • inflexibleEach lock has only a single condition (an object). Read/write locks are more flexible
  • There is no way to know if the lock was successfully acquiredIn contrast, Lock can get the state. If the Lock is successfully obtained,…. If the obtaining fails, go to…..

The defect of the Lock

  • Synchronized mode does not require the user to manually release the lock. When the synchronized method or synchronized code block is executed, the system will automatically release the occupation of the lock by the thread. Lock, the Lock must be actively released and will not be automatically released when an exception occurs. Therefore, in general, using a Lock must be done ina try{}catch{} block, and the Lock release must be done ina finally block to ensure that the Lock is released and prevent deadlocks.

2.3 CAS

Reference: CAS mechanism, the cornerstone of concurrent programming

CAS, short for Compare and Swap, has three core parameters in this mechanism:

  • The value of the shared variable stored in main memory: V (usually this V is the address value of memory, from which the value can be obtained)
  • The copy value of the shared variable in working memory, also known as the expected value: A
  • The latest value to update the shared variable to: B

V is stored in main memory. To use V in A thread, V must first be read from main memory into the thread’s working memory A, then calculated to B, and finally written back to MEMORY V. This is true when multiple threads share V values. The core of CAS is to compare whether the value of A and V are the same before writing the value of B to V. If they are different, it proves that the value of V has been changed by other threads at this time, and then assign the value of V to A and recalculate to get B. If they are the same, then assign the value of B to V.

It is worth noting that this step in the CAS mechanism is atomic (atomic operations provided from the instruction level), so the CAS mechanism can solve the atomicity problem of multithreaded concurrent programming reading and writing to shared variables.

disadvantages

ABA problem: CAS checks to see if the value of the variable has been changed. If not, it updates the value, causing A problem. First the value is A, then B, and then A. It is true that the value has not been changed since the final value is still A, but it has been changed. To solve this problem, add A version number to each operation. Each operation has two values, one version number and some value, and the problem becomes 1A — >2B — >3A. The JDK provides an AtomicStampedReference class to address ABA problems. The Pair class contains two attributes, one representing the version number and the reference number. In compareAndSet, the reference is checked first and then the version number flag is checked.

It seems that CAS is more efficient than locks, moving from a blocking mechanism to a non-blocking mechanism, reducing the wait time between threads. Each method cannot be absolutely better than the other. In the case of a high degree of contention between threads, if CAS is used, there will be many threads competing each time, that is, the CAS mechanism cannot be updated successfully. In this case, the CAS mechanism keeps retrying, which can be CPU intensive. As you can see, CAS is a good choice if there is little contention between threads. But if there is a lot of competition, using locks may be a better option.

The CAS mechanism in Java only guarantees atomicity for shared variable operations, not atomicity for code blocks. (This is where synchronied locking comes in, and you’ll see that synchronied is really versatile.)

advantages

  • The atomicity of variable operation can be guaranteed.
  • When the concurrency is not very high, CAS mechanism is more efficient than locking mechanism.
  • The CAS mechanism is also efficient when threads occupy shared resources for a short time.

2.4 AQS

Reference: see AQS principle and application from the realization of ReentrantLock

AQS full name: AbstractQueuedSynchronizer, abstract queue type synchronizer AQS is an abstract class, it defines a set of synchronizer framework for multithreaded access to a Shared resource. Colloquially, AN AQS is like a queue manager, and when multiple threads operate, these threads are queued to manage.

AQS implements synchronization by maintaining two variables

  • state

AQS uses a volatile private variable to indicate synchronization status, with state=0 indicating that the lock was released and state>0 indicating that the lock was acquired.

  • FIFO synchronization queue

AQS through the built-in FIFO synchronization queue, to achieve thread queuing. If the thread fails to obtain the current synchronization status, AQS encapsulates the information of the current thread into a Node Node and adds it to the synchronization queue through CAS. When the synchronization state is released, the thread in the queue wakes up and tries to get the synchronization state again.