Author: Qian Jue

Email address: [email protected]

preface

In every interview, there is always an interviewer who throws out the deadly triple triple of high concurrency, high availability and high performance

First of all, we need to know which locks are required in Java. Qian Jue thinks the following picture can clearly explain the difference between Java locks (the picture is from the network, if there is infringement, please contact me to delete it by email).

Now let’s take you through the “lock” in Java

Pessimistic locks and optimistic locks

Pessimistic lock concept: always assume the worst case scenario, every time I take data, I think someone else will change the data, so I have to lock it, the other person has to wait until I release the lock. Database row locks, table locks, read locks, write locks are all this way. The implementation classes for Synchronized and Lock in Java are also pessimistic locking concepts.

The concept of optimistic locking: always assume the best case scenario, every time you take the data, you assume that no one else will change the data, so you will not lock it, but when you update it, you will determine whether anyone has changed it in the meantime; Generally implemented based on the version number mechanism. The most common form of optimistic locking in Java is the CAS algorithm.

Based on the above concepts, we can simply know the application scenarios of optimistic locking and pessimistic locking

  1. Optimistic locking is suitable for reading too much and writing too little, because reading without locking can greatly improve the system performance.
  2. Pessimistic locking applies to the situation where there are too many writes and too few reads, because the lock can be acquired immediately after it is released.

It might be a little confusing to get straight to the concept, but let’s look at how it’s called in Java

// Use synchronized to implement pessimistic locks
public synchronized  void test(a){// Perform the corresponding operation}
// Pessimistic Lock is implemented with Lock
Lock lock = new ReentrantLock();
public void testLock(a){   
    lock.lock();
    //TODO performs the corresponding operation
    lock.unlock();
}
/ / optimistic locking
AtomicInteger atomicInteger = new AtomicInteger();
public void testCAS(a){
  atomicInteger.incrementAndGet();
}
Copy the code

See the above call we can see that pessimistic lock is directly lock to ensure the synchronization of resources, at this time many friends will ask why optimistic lock without lock can also achieve resource synchronization, yes, why, and see the analysis of Thousand jue.

Why can optimistic lock achieve resource synchronization without locking?

We said at the beginning because the main implementation of optimistic locking is the CAS algorithm.

Java.util.concurrent.*. The following classes use the CAS algorithm to implement an optimistic lock that differs from synchronouse locking.

CAS has three operands, the memory value V, the old expected value A, and the new value B to modify. Change the memory value V to B if and only if the expected value A and memory value V are the same, otherwise nothing is done.

This is the CAS that allows us to “lock” without locking. CAS is strong, but it has several problems

  1. ABA problem. This is because CAS needs to check if the value has changed and update it if it has not. However, if A value is A, changes to B, and then changes to A, CAS checks that it has not changed, but actually has changed. The solution to the ABA problem is to use version numbers. Append the version number to the variable, increment the version number by one each time the variable is updated, and a-b-A becomes 1A-2B-3a.

    Since Java1.5, the JDK atomic package has provided a class AtomicStampedReference to address ABA issues. The compareAndSet method of this class first checks whether the current reference is equal to the expected reference, and whether the current flag is equal to the expected flag, and if all are equal, sets the reference and flag values to the given updated value atomically.

    Reference documentation about ABA: blog.hesey.net/2011/09/res…

  2. Long cycle time and high overhead. Spin CAS, if unsuccessful for a long period of time, can impose a significant execution overhead on the CPU.

  3. Atomic operations of only one shared variable are guaranteed. When performing operations on a Shared variables, we can use a loop CAS way to ensure that atomic operation, but for more than one Shared variables, circulation CAS will not be able to ensure the atomicity of operation, this time can use the lock, or there is a tricky way, is to combine multiple Shared variables into a Shared variable to operation. Let’s say we have two shared variables I =2,j=a, combine ij=2a, and then use CAS to manipulate ij.

Since Java1.5, the JDK has provided the AtomicReference class to ensure atomicity between reference objects. You can place multiple variables in an object to perform CAS operations.

Spinlocks and adaptive spinlocks

Spin lock: To avoid frequent suspension and recovery while a thread is acquiring a synchronous resource, a thread that would otherwise have to wait can be kept in a loop to acquire the lock. This is called a spin lock.

Adaptive spin lock: The adaptive spin lock is reflected in the spin time is no longer fixed. If, on the same lock object, the spin thread has just acquired the lock before, and the thread holding the lock is now running, the virtual machine assumes that the spin is also likely to succeed, allowing the thread to wait for a relatively long time, such as 100 cycles. On the other hand, if a lock spin is rarely successful, it is possible to omit the spin process when acquiring the lock later to avoid wasting processor resources.

The above concepts come from the Internet.

Disadvantages of spin locking: While spin waiting avoids the overhead of thread switching, it consumes processor time. If the lock is held for a short period of time, the spin wait works very well. Conversely, if the lock is held for a long time, the spinning thread is a waste of processor resources. Therefore, the spin wait time must be limited, and the thread should be suspended if the lock is not successfully acquired if the spin exceeds the limit (the default is 10 spins, which can be changed using -xx :PreBlockSpin). In JDK6, spin lock is enabled by default.

The implementation principle of the spin lock is also CAS. The implementation principle of the optimistic lock is CAS can be unlocked. The spin lock is an infinite loop until its value changes successfully.

There are three common types of spinlocks: TicketLock, CLHlock, and MCSlock.

Unlocked and biased locks and lightweight and heavyweight locks

These four locks are actually the four states of the lock. At this time, I believe there must be readers who ask what the state of the lock is and where the lock exists.

Don’t worry, don’t worry, go with Qian – chueh, offer get hand cramps.

Where is the lock stored?

The lock exists in the Mark Word in the header of the Java object. By default, Mark Word stores not only the lock flag bits, but also information such as object hashCode. At runtime, the Mark Work store contents are modified based on the lock state. If the object is an array type, the virtual machine stores the object header with 3 word widths, and if the object is not an array type, the virtual machine stores the object header with 2 word widths. In a 32-bit VIRTUAL machine, a word is four bytes, or 32 bits. For more information about objects, see the Java Virtual Machine article.

There’s something about locks in the Mark Word

Store content Sign a state
Object hashCode, object generation age, whether biased lock (0) 01 unlocked
A pointer to a lock record 00 Lightweight lock
Pointer to a heavyweight lock 10 Heavyweight lock
Biased thread ID, biased timestamp, object generation age, whether biased lock (1) 01 Biased locking

No lock: resources are not locked, so that all threads can access resources, but only one resource can be modified.

Biased locking: Threads do not have race conditions in most cases, and using synchronization costs performance. Biased locking is an optimization of locks that eliminates synchronization and improves performance. When a thread acquires a lock, it sets the lock flag bit of the object header to 01 and goes into bias mode. Biased locks allow one thread to hold the lock until it is contested by other threads.

Lightweight lock: When thread 1 acquises a biased lock, thread 2 enters a race to acquire the lock held by thread 1. The biased lock is upgraded to a lightweight lock, and other threads attempt to acquire the lock through spin.

Heavyweight lock: When the spin exceeds a certain number of times, or when one thread is holding the lock, one thread is spinning, and a third thread is calling, the lightweight lock is upgraded to heavyweight lock, and all the threads waiting for the lock are blocked.

The upgrade process of the overall lock status is as follows: partial lock —-> lightweight lock —-> Heavyweight lock

Fair locks and unfair locks

Fair lock: Every thread gets the lock.

Unfair lock: There is no guarantee that every thread will get the lock.

I’m a little confused by the language description, for example.

A fair lock is when you go to the cafeteria to get your food and if you stand in line to get your food it’s a fair lock.

Unfair lock is when you go to the canteen to make a meal, you can not queue, if a person in front of a meal, you can directly make a meal, no matter how many people did not make a meal. That would be an unfair lock.

Java in the fair lock, not fair lock implementation

/ / fair lock
ReentrantLock lock = new ReentrantLock(true);
// Unfair lock
ReentrantLock lock = new ReentrantLock(false);
Copy the code

I won’t talk about how it works, but if you want to know why it works, you can leave a comment.

The applicable scenario is: if the thread takes longer than the thread switching time, it is better to use a fair lock, otherwise, it is better to use an unfair lock.

Reentrant and non-reentrant locks

A reentrant lock is a reentrant lock that can be used on the inside of a method after it has been used on the outside without deadlocking (provided it is the same object or class). Synchronized and ReentrantLock are reentrant locks.

You might be a little confused looking at the concept, but it’s pretty simple looking at the implementation.

public class Test implements Runnable{
    public static void main(String []args){
        Test test = new Test();
        for(int i = 0; i < 5; i++){
            newThread(test).start(); }}@Override
    public void run(a) {
        out();
    }

    public synchronized void out(a){
        System.out.println(Thread.currentThread().getName());
        in();
    }

    public synchronized void in(a){ System.out.println(Thread.currentThread().getName()); }}Copy the code

// The thread is not blocked

Thread-2 Thread-2 Thread-4 Thread-4 Thread-3 Thread-3 Thread-1 Thread-1 Thread-0 Thread-0

A non-reentrant lock is a non-repeatable lock that cannot be used on the inside after the lock is used on the outside method. In this case, the lock will block until your outside lock is released. A deadlock can occur.

The non-reentrant lock is implemented as follows:

public class NoLock {
    private boolean isLocked = false;
    public synchronized  void lock(a) throws InterruptedException {
        while (isLocked){
            wait();
        }
        isLocked = true;
    }
    public synchronized void unlock(a){
        isLocked = false; notify(); }}Copy the code

Exclusive locks and shared locks

Exclusive lock: This lock can only be held by one thread at a time. Synchronized and ReentrantLock are exclusive locks

Shared lock: This lock can be shared by multiple threads. The thread that acquires the shared lock can only read the data, not modify it.

ReentrantReadWriteLock has two locks: ReadLock and WriteLock. A ReadLock and a WriteLock are known as read-write locks. Read locks are shared locks, while write locks are exclusive locks

conclusion

Java “lock” things to this end, there are a lot of principles of things, qian Jue did not in-depth introduction, on the one hand because of my level is not enough, on the one hand because of the length of the problem, if the reader read this article have any questions can leave a message to tell me.

Finally, beseech everybody sees this article to feel to write of still go, troublesome troublesome your little hand dot concern, dot praise, your praise and concern are the power that 1000 jue writes.

2019 ends, I hope 2020 can be better, I wish everyone a happy New Year’s day.