Personal blog

www.milovetingting.cn

Java Multithreading (2)

preface

This article takes notes for learning about Java, refer to the following resources :github.com/Snailclimb/… Thanks for sharing!

JAVA lock

Optimistic locking

Optimistic locking is a kind of optimistic thought that reading more than writing less, meet low likelihood of concurrent write, every time to fetch the data that other people will not change, so will not lock, but at the time of update will assess the during that time, people have to update the data, at the time of writing in a first read the current version number, and then lock operation (compared with the previous version number, Update if it is the same), and repeat the read-compare-write operation if it fails.

Optimistic locking in Java is basically implemented through CAS operation, which is an updated atomic operation. The CAS operation compares the current value with the passed value. If the current value is the same as the passed value, it will be updated, otherwise it will fail.

Pessimistic locking

Pessimistic locking is a pessimistic thinking, that is, the likelihood of concurrent writing is high, and every time I go to fetch data, I think someone else will modify it, so every time I read or write data, I will lock it, so that someone else will try to read or write the data, and the data will be blocked until I get the lock. Pessimistic lock in Java is Synchronized. In AQS framework, cas optimistic lock is first tried to obtain the lock. If it fails to obtain the lock, it will be converted to pessimistic lock, such as RetreenLock.

spinlocks

Spinlocks principle is very simple, if the thread holding the lock can lock is released in a very short time resources, and the thread lock wait for competition there is no need to do between kernel mode and user mode switch into the block pending state, they just need to wait for a while (spin), such as thread holding the lock immediately after releasing the lock locks, thus avoiding the consumption of user and kernel thread switching

If the thread can’t get the lock, it can’t use the spin to do idle work, so it needs to set a maximum spin wait time

If the thread holding the lock executes for longer than the maximum spin wait time and does not release the lock, other threads contending for the lock still cannot acquire the lock within the maximum wait time, then the contending thread will stop spinning and enter the blocking state

Advantages and disadvantages of spin locks

Spinlocks minimize thread blocking, which is a significant performance improvement for code blocks that are less competitive for locks and have a very short lock duration, because the spin cost is less than the cost of blocking, suspending and waking operations that cause two context switches

But if the lock of the competition is intense, or thread holding the lock need long time to occupy the lock synchronization block, this time is not suitable for using a spin lock, because of the spin lock before acquiring a lock is CPU doing this all the time, for the XX XX, competition at the same time a large number of threads in a lock, and can lead to acquiring a lock for a long time, The cost of thread spin is greater than the cost of thread blocking and suspension operations, and other threads that need CPU cannot obtain CPU, resulting in CPU waste. So in this case we want to turn off the spin lock;

Spinlock time threshold (1.6 introduced adaptive spinlock)

The purpose of a spin lock is to hold CPU resources until the lock is acquired. But how do you choose when to execute the spin? If the spin execution time is too long, a large number of threads in the spin state will occupy CPU resources, which will affect the overall system performance. So the cycle of spin is extra important!

In JDK 1.6, adaptive spin locking was introduced. Adaptive spin locking means that the time of spin on the same lock is not fixed, but is determined by the state of the previous spin on the lock and the owner of the lock. It is generally considered that the time for a thread context switch is the optimal time, and the JVM is optimized for the current CPU load. If the average load is less than CPUs, the thread spins; if more than (CPUs/2) threads are spinning, the thread blocks directly afterwards. If the thread that is spinning finds that its Owner has changed, it will delay spin time (spin count) or block. If the CPU is in power-saving mode, it will stop spinning. The worst case of spin time is CPU memory delay (CPU A stores A piece of data, and CPU B gets the data directly). Differences between thread priorities are appropriately waived when spinning.

The opening of the spin lock

-xx :+UseSpinning enabled in JDK1.6;

-xx :PreBlockSpin=10;

After JDK1.7, this parameter is removed and controlled by JVM.

A Synchronized synchronous lock

Synchronized treats any non-null object as a lock. It is an exclusive pessimistic lock and a reentrant lock

Range of Synchronized

  1. When applied to a method, it locks an instance of the object (this);

  2. When used for static methods, the Class instance is locked, and the permanent band is shared globally because the data related to the Class is stored in PermGen (in JDK1.8, metaspace). Therefore, the static method lock is equivalent to a global lock of the Class, and will lock all threads that call the method.

  3. Synchronized, when applied to an object instance, locks all blocks of code that are locked on that object. It has multiple queues, and when multiple threads access an object monitor together, the object monitor stores these threads in different containers.

Synchronized core components

  1. Wait Set: this is where threads that blocked calling the Wait method are placed;

  2. Contention List: Contention queue where all threads requesting locks are placed first;

  3. Entry List: Threads in the Contention List that qualify as candidate resources are moved to the Entry List;

  4. OnDeck: At most one thread is competing for a lock resource at any given time. This thread is called OnDeck;

  5. Owner: The thread that has obtained the resource is called Owner.

  6. ! Owner: specifies the current thread that releases the lock.

Synchronized implementation

  1. The JVM fetches data one at a time from the end of the queue for lock contention candidates (onDecks), but in the case of concurrency, the ContentionList is CAS accessed by a large number of concurrent threads. To reduce contention on tail elements, The JVM moves a subset of threads into the EntryList as candidate contention threads

  2. In unlock, the Owner thread migrates some of the ContentionList threads to EntryList and designates one of the EntryList threads as an OnDeck thread.

  3. Instead of passing the lock directly to the OnDeck thread, the Owner thread gives the lock contention to OnDeck, which needs to recontest the lock. This sacrifices some fairness, but can greatly improve the throughput of the system. In the JVM, this choice behavior is also called “competitive switching.”

  4. The OnDeck thread becomes the Owner thread after acquiring the lock resource, while the thread that does not acquire the lock resource remains in the EntryList. If the Owner thread is blocked by wait, it is placed in the WaitSet queue until it is awakened by notify or notifyAll and re-entered into the EntryList

  5. ContentionList, EntryList, and WaitSet threads are all blocked by the operating system (pthread_mutex_lock in Linux).

  6. Synchronized is an unfair lock. Synchronized when a thread enters the ContentionList, the waiting thread will first try to acquire the spin lock. If it fails to acquire the lock, it will enter the ContentionList, which is obviously unfair to the thread that has entered the queue. Another unfair thing is that the thread that spins to acquire the lock may also directly preempt the lock resource of the OnDeck thread

  7. Each object has a Monitor object, and locking competes with the monitor object. Block locking is implemented by adding monitorenter and Monitorexit directives, respectively. Method locking is determined by a marker bit

  8. Synchronized is a heavyweight operation that requires the invocation of the relevant interface of the operating system. Its performance is inefficient and it may take more time to lock a thread than a useful operation

  9. Java1.6, synchronized carried out a lot of optimization, including adaptive spin, lock elimination, lock coarser, lightweight lock and biased lock, efficiency has been substantially improved. In Java1.7 and 1.8, the implementation mechanism of this keyword is optimized. Biased locks and lightweight locks were introduced. Both have token bits in the object header and do not need to be locked by the operating system

  10. Locks can be upgraded from biased locks to lightweight locks to heavyweight locks. This escalation process is called lock inflation;

  11. Bias locking and lightweight locking are enabled by default in JDK 1.6. Bias locking can be disabled by -xx: -usebiasedlocking

ReentrantLock

ReentantLock inherits the interface Lock and implements the method defined in the interface. It is a reentrant Lock. In addition to completing all the work that synchronized can do, ReentantLock also provides methods such as responsible interrupt Lock, polling Lock request, timing Lock and so on to avoid multi-thread deadlock.

The main method of the Lock interface

  1. Void lock(): When this method is executed, if the lock is idle, the current thread acquires it. Conversely, if the lock is already held by another thread, the current thread is disabled until the current thread acquires the lock.

  2. Boolean tryLock() : Obtain the lock if it is available and return true immediately, return false otherwise. The difference between this method and lock() is that tryLock() only “tries” to acquire the lock, and if the lock is unavailable, it does not cause the current thread to be disabled and the current thread to continue executing the code. The lock() method, on the other hand, must acquire the lock. If the lock is not available, it waits, and the current thread does not proceed until the lock is acquired.

  3. Void unlock() : When this method is executed, the lock held by the current thread is released. The lock can only be released by the holder, and if the thread does not hold the lock and executes this method, an exception may occur.

  4. Condition newCondition() : Condition object that gets the waiting notification component. This component is bound to the current lock, and the current thread cannot call the await() method of the component until it has obtained the lock, which will release the lock.

  5. GetHoldCount () : Queries the number of times the current thread holds the lock, that is, the number of times the thread executes the lock method.

  6. GetQueueLength () : Returns an estimate of the number of threads waiting to acquire the lock, such as starting 10 threads and 1 thread acquiring the lock, which returns 9

  7. GetWaitQueueLength :(Condition Condition) returns the estimate of the number of threads waiting for the given Condition associated with this lock. For example, if 10 threads are using the same condition object and all 10 threads are executing the await method of the condition object, executing this method returns 10

  8. HasWaiters (Condition Condition) : Loves his work: Queries whether there are threads waiting on a given Condition (Condition) associated with this lock, and how many threads execute the Condition. Await method for the specified contidion object

  9. HasQueuedThread (Thread Thread) : Queries whether a given Thread is waiting to acquire this lock

  10. HasQueuedThreads () : Whether there are threads waiting for this lock

  11. IsFair () : indicates whether the lock isFair

  12. IsHeldByCurrentThread () : Whether the current thread holds the lock. The thread executes the lock method before and after false and true, respectively

  13. IsLock () : Whether this lock is occupied by any thread

  14. LockInterruptibly () : Acquires the lock if the current thread is not interrupted

  15. TryLock () : Attempts to acquire the lock, only if the lock is not occupied by the thread when called

  16. TryLock (Long Timeout TimeUnit Unit) : Obtains the lock if it is not held by another thread within a given wait time.

Not fair lock

JVM mechanisms that allocate locks randomly and nearby are called unfair locks, and ReentrantLock provides an initialization method in the constructor for whether or not the lock is fair, which defaults to unfair locks. The actual execution of unfair locks is far more efficient than that of fair locks. Unless the program has special needs, the allocation mechanism of unfair locks is most commonly used.

Fair lock

A fair lock means that the lock allocation mechanism is fair. Usually, the thread that requests the lock first will be allocated the lock first. ReentrantLock defines a fair lock by providing an initialization method in the constructor to determine whether the lock is fair

Already with synchronized

  1. ReentrantLock uses methods Lock () and unlock() to lock and unlock. Unlike synchronized, which is automatically unlocked by the JVM, ReentrantLock needs to be unlocked manually. To prevent the program from being able to unlock properly due to an exception, using ReentrantLock you must do the unlock operation in the finally control block.

  2. The advantages of ReentrantLock over synchronized are interruptible, fair locking, and multiple locks. You need to use ReentrantLock in this case.

public class MyService {
    private Lock lock = new ReentrantLock();
    //Lock lock=new ReentrantLock(true); / / fair lock
    //Lock lock=new ReentrantLock(false); // Unfair lock
    private Condition condition=lock.newCondition();/ / create the Condition
    public void testMethod(a) {
        try {
        lock.lock();/ / lock lock
        //1: wait
        / / System. Out. Println (" began to wait ");
        condition.await();
        // To make the thread wait by creating the Condition object, the lock must be obtained by executing the lock.lock method
        //:2: the signal method wakes up
        condition.signal();// The condition's signal method wakes up the wait thread
        for (int i = 0; i < 5; i++) {
            System.out.println("ThreadName=" + Thread.currentThread().getName()+ ("" + (i + 1))); }}catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally{ lock.unlock(); }}}Copy the code

Condition and Object lock methods are different

  1. Condition’s AWIAT method is equivalent to Object’s WAIT method

  2. The Condition signal method is equivalent to the notify method of Object

  3. The signalAll method of Condition is equivalent to notifyAll method of Object

  4. The ReentrantLock class can wake up threads with specified conditions, whereas Object wakes up randomly

TryLock and the difference between lock and lockInterruptibly

  1. TryLock (long timeout,TimeUnit unit), which increases the time limit, returns false if the lock has not been obtained after that period

  2. Lock returns true if the lock is available, or wait until the lock is available

  3. Lock and lockInterruptibly. If two threads execute these two methods separately and interrupt the two threads, Lock does not throw an exception, while lockInterruptibly does.

Semaphore Semaphore

Semaphore is a counting – based Semaphore. It can set a threshold at which multiple threads compete for a license signal, complete their own request and return it, and block the request signal after the threshold is exceeded. Semaphore can be used to build object pools, resource pools, etc., such as database connection pools

Implement mutex (counter 1)

We can also create a Semaphore with a count of one as a mutex like mechanism. This is also called a binary Semaphore, representing two mutually exclusive states

// Create a semaphore object with a count threshold of 5
// Only 5 threads can access it simultaneously
Semaphore semp = new Semaphore(5);
try { // Apply for permission
    semp.acquire();
    try {
        // Business logic
    } catch (Exception e) {

    } finally {
        // Release permissionsemp.release(); }}catch (InterruptedException e) {

}
Copy the code

Semaphore with already

Semaphore does almost all the work of ReentrantLock, using similar methods, acquire() and release() to acquire and release critical resources. , checked by actual measured Semaphone. Acquire () method of the lock, the default for a response to an interrupt and already. LockInterruptibly () function are same, that is to say, waiting for the process of critical resources can be Thread. The interrupt () method of the interrupt

Semaphore also implements polling lock requests and timing locks, which are almost identical to ReentrantLock except that the method name tryAcquire is different from tryLock. Semaphore also provides mechanisms for fair and unfair locks, which can also be set in constructors

Semaphore’s lock release is also done manually, so like ReentrantLock, the lock release must be done in the finally code block to prevent the thread from throwing an exception and failing to release the lock properly.

AtomicInteger

First of all, AtomicInteger, a class that provides Integer atomic operations, common AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference, etc., their implementation principle is the same. The difference is the operand type. An AtomicReference is also an exciting way to translate all operations on an object into atomic operations.

We know that in multithreaded programs, operations such as ++ I or I ++ are not atomic and are one of the unsafe thread operations. Normally we would use synchronized to turn this into an atomic operation, but the JVM specifically provides synchronization classes for this type of operation to make it easier to use and make the program run more efficiently. According to relevant data, AtomicInteger usually performs several times better than ReentantLock.

Reentrant lock (recursive lock)

This article is about reentrantlocks in the broad sense, not reentrantLocks in JAVA alone. A reentrant lock, also known as a recursive lock, means that after an outer function of the same thread acquires the lock, the inner recursive function still has the code to acquire the lock, but is not affected. ReentrantLock and synchronized are both reentrant locks in JAVA environments.

Fair and unfair locks

Fair Lock

Check whether there are queued threads before locking. Priority is given to queued threads. First come, first served

Nonfair Lock

When the lock is added, it does not consider the queuing problem, and directly tries to obtain the lock. If the lock cannot be obtained, it will automatically wait at the end of the queue

  1. The performance of an unfair lock is 5 to 10 times higher than that of a fair lock, because a fair lock requires the maintenance of a queue with multiple cores

  2. Synchronized is an unfair lock in Java, and the default lock() method of ReentrantLock uses an unfair lock.

ReadWriteLock read-write lock

In order to improve the performance, Java provides read and write lock, read lock is used in the read place, write lock is used in the write place, flexible control, if there is no write lock, read is non-blocking, to a certain extent, improve the efficiency of the program. Read locks are divided into read locks and write locks. Multiple read locks are not mutually exclusive. Read locks and write locks are mutually exclusive, which is controlled by the JVM itself

Read lock

If your code reads only data, which can be read by many people at the same time, but cannot be written at the same time, lock it

Write lock

If your code modifies data and only one person is writing it and cannot read it at the same time, then write locks. In short, read while lock, write while lock!

Read-write lock a Java interface in Java. The util. Concurrent. The locks. ReadWriteLock, also have specific implementation ReentrantReadWriteLock

Shared and exclusive locks

Java provides two locking modes: exclusive lock and shared lock.

An exclusive lock

In exclusive lock mode, only one thread can hold the lock at a time. ReentrantLock is a mutex implemented in exclusive mode. An exclusive lock is a pessimistic and conservative locking strategy that avoids read/read conflicts. If one read-only thread acquires the lock, all other readers must wait, which limits unnecessary concurrency because the read operation does not affect data consistency

A Shared lock

A shared lock allows multiple threads to obtain the lock and concurrently access shared resources, such as ReadWriteLock. The shared lock is an optimistic lock, which relaxes the locking policy and allows multiple read threads to access the shared resource simultaneously

  1. The internal Node class of AQS defines two constants, SHARED and EXCLUSIVE, that identify the lock acquisition mode of the waiting thread in the AQS queue.

  2. Java concurrency provides ReadWriteLock, a read-write lock. It allows a resource to be accessed by multiple read operations or by a write operation, but not both at the same time.

Mutex Lock

Synchronized is implemented through a lock inside an object called a monitor. But the essence of the monitor Lock depends on the underlying operating system Mutex Lock to implement. However, the operating system realizes the switch between threads, which requires the conversion from user state to core state. This cost is very high, and the conversion between states takes a relatively long time, which is why Synchronized has low efficiency. Therefore, this type of Lock, which relies on the implementation of the operating system Mutex Lock, is called a “heavyweight Lock.” At the heart of all the JDK optimizations for Synchronized are efforts to reduce the use of this heavyweight lock. After JDK1.6, “lightweight locking” and “biased locking” were introduced to reduce the performance cost of acquiring and releasing locks and improve performance.

Lightweight lock

There are four types of lock states: unlocked, biased, lightweight, and heavyweight.

Lock escalation

As locks compete, locks can be upgraded from biased locks to lightweight locks to heavyweight locks (but locks are upgraded in one direction, meaning they can only be upgraded from low to high, with no degradation).

“Lightweight” is in contrast to traditional locks implemented using operating system mutex. However, it is important to note that lightweight locks are not intended to replace heavyweight locks. They are intended to reduce the performance cost of traditional heavyweight locks without multi-threaded competition. Before explaining how lightweight locks are executed, it is important to understand that lightweight locks are adapted to situations where threads alternately execute synchronized blocks. If the same lock is accessed at the same time, it will cause the lightweight lock to expand into a heavyweight lock.

Biased locking

Previous research by Hotspot authors has found that in most cases locks are not only not contested by multiple threads, but are always acquired multiple times by the same thread. The purpose of biased locking is to eliminate the overhead of lock reentrant (CAS) after a thread has acquired the lock, seemingly favoring the thread. Biased locking is introduced in order to minimize unnecessary lightweight lock execution paths without multi-threaded contention, because the acquisition and release of lightweight locks depend on multiple CAS atomic instructions. The biased lock only needs to rely on the CAS atomic instruction once the ThreadID is replaced (since the biased lock must be revoked once the multithreading competition occurs, the performance loss of the cancellation operation of the biased lock must be less than the performance consumption of the saved CAS atomic instruction). As mentioned above, lightweight locking is intended to improve performance when threads alternately execute synchronized blocks, while biased locking is intended to further improve performance when only one thread executes synchronized blocks.

Segmented lock

Segmented locking is also not an actual lock, but an idea. ConcurrentHashMap is the best practice for learning segmented locking

Lock the optimization

Reduce lock holding time

Lock only on programs that require thread-safety

Reduce lock granularity

Splitting large objects (which may be accessed by many threads) into smaller objects greatly increases parallelism and reduces lock contention. Reduce the lock competition, biased lock, lightweight lock success rate will be improved. The most typical example of reducing lock granularity is ConcurrentHashMap

Lock the separation

The most common type of lock separation is ReadWriteLock, which is separated into read lock and write lock according to the function. In this way, the read lock is not mutually exclusive, but the read lock is mutually exclusive, and the write lock is mutually exclusive, which ensures thread safety and improves performance. The read-write separation idea can be extended so that locks can be separated as long as the operations do not affect each other. For example, LinkedBlockingQueue is taken from the head and data is put from the tail

Lock coarsening

In general, to ensure effective concurrency between multiple threads, each thread is required to hold the lock for as short a time as possible, that is, the lock should be released immediately after the use of common resources. However, everything has a certain degree, if the same lock is constantly requested, synchronized and released, it will consume valuable resources of the system, but not conducive to performance optimization

Lock elimination

Lock elimination is a matter at the compiler level. When the just-in-time compiler finds objects that cannot be shared, it can eliminate locking operations on those objects, mostly because of programmer coding irregularities