Java lock

1. General classification of locks

2. Optimistic and pessimistic locks

Pessimistic lock: For the use of the same data, pessimistic lock will assume that there will be other threads to access the process, so it will add a lock in advance. Synchronized and Lock are pessimistic locks in Java.

Optimistic lock: In the process of using data, only when the data needs to be modified, the latest data in memory is compared to whether it has been modified or not. CAS in Java is a kind of optimistic lock.

Java optimistic lock is the most intuitive Atomic Atomic operation class, such as AtomicInteger, AtomicLong, etc., in the increment function, CAS is used

Addressing an unsafe environment, the static AtomicInteger class uses an unsafe address to retrieve data from memory. Even though unsafe is a broadening object, valueoffset is the address of an AtomicInteger value.

  • Broadening: Accesses and manipulates memory data.
  • ValueOffset: Stores the offset of a value in AtomicInteger.
  • Value: Stores the int value of AtomicInteger, which requires the volatile keyword to be visible across threads.

// ------------------------- JDK 8 -------------------------
// AtomicInteger increment method
public final int incrementAndGet(a) {
  return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

// Unsafe.class
public final int getAndAddInt(Object var1, long var2, int var4) {
  int var5;
  do {
      var5 = this.getIntVolatile(var1, var2);
  } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  return var5;
}

// ------------------------- OpenJDK 8 -------------------------
// Unsafe.java
public final int getAndAddInt(Object o, long offset, int delta) {
   int v;
   do {
       v = getIntVolatile(o, offset);
   } while(! compareAndSwapInt(o, offset, v, v + delta));return v;
}


Copy the code

When we call the increment method, we call the getAndAddInt method, which has a do{} while() in it. Do fetches the value from memory, which we call the old CAS value.

CompareAndSwapInt uses a CPU instruction, CMPXCHG, to perform the atomic comparison + swap operation.

Problems with CAS

  1. ABA problem: after Java1.5, the AtomicStampedReference class was introduced, where you can add a version number to each value
  2. Long cycle time: This is a parameter that allows you to set the number of spins in the JVM. The default is 10 spins. Since version 1.6, adaptive spin locks have been introduced, which can be dynamically adjusted based on the time of the last spin and the circumstances in which the lock was held.
  3. Atomic operations of only one variable can be guaranteed. CAS atomic operations can only be performed on one variable. If there are multiple variables, they cannot be used.

3. Java object Model

For 64-bit operating systems, native objects consist of object headers, actual data, and object padding:

  • Java object headers: Contains a Mark Word, a Klass Pointer, and a length that is only useful if the object is an array. There are two modes: compressed mode and uncompressed mode. In compressed mode, the pointer takes only 4 bytes, which makes up 12 bytes in total. In uncompressed mode, the pointer takes up 16 bytes.
  • The actual data
  • Object with a 64 – bit Java virtual machine, 8 byte alignment, so less than 8 multiples, complement, the role of the padding is this, secondly, 8 bytes cache line corresponds to a CPU instructions, if less than 8 bytes, easy to cause false sharing, multiple threads to use easy to cause competition, thus make up 8 bytes, reducing competition, To improve efficiency, ring queues in Java take advantage of this feature.

Java object head

Java object header Mark Word components:

Java objects have four lock states, no lock, biased lock, lightweight lock, and heavyweight lock

No lock: The lock flag bit is 01 and the bias flag is 0

Biased lock: the lock marker bit is 01, and the bias marker is 1. Biased lock only when other threads try to compete for biased lock, the thread holding biased lock will release the lock, and the thread will not release biased lock actively. Advantages: It is easier to add and unlock the partial lock, and it is faster, close to the nanosecond level. Disadvantages: Once the competition occurs, the lock will be revoked in the process of lock expansion, bringing certain loss.

Lightweight lock: the lock flag bit is 00. Once there is a competition between biased locks, CAS will spin the lock. If the lock is successfully added, it will be upgraded to lightweight lock. Advantages: Through the way of spin lock, will not block; Disadvantages: Spin will bring CPU consumption, and easy to enter a long time of spin; Suitable scenario: Synchronization code block is short, and the execution is fast, suitable for the use of spin lock.

Heavyweight lock: lock marked as 10, in the process of the spin lock, if the number of spin cannot lock to succeed more than 10 times, will expand to heavyweight, lock heavyweight lock will depend on the system of the operating system calls, as the kernel are needed to apply resources, to kernel mode and user mode conversion, thus consumes a lot of CPU resources. Advantages: don’t need to spin, faults: a thread is blocked, applicable scenarios: high throughput (in the process of waiting for the thread scheduling is done by the operating system hangs and awakening, without CPU spin waiting, CPU to do other things, the overall throughput will be higher than other lock), or the execution time of synchronized code block.

4. Fair locks and unfair locks

Fair and non-fair locks are generally used in ReentrantLock. Synchronized is a non-fair lock. When acquiring a lock, all threads compete and wake up one at random.

In ReentrantLock, fair and unfair locks are implemented by determining whether the current thread is the first thread in the queue when acquiring the lock. You can refer to the source code

5. Reentrant and non-reentrant locks

Reentrant lock: in the same object, the same lock, and has acquired the premise of the lock, the same thread, if the execution of other methods to obtain the lock or synchronization code segment, without queuing, can directly obtain the lock.

Non-reentrant lock: A lock must be acquired every time. A non-reentrant lock is likely to cause A deadlock. For example: Class A has two methods for locking, method 1 and method 2


class A {
    public synchronized void fun1(a){
        fun2();
    }
    
    public synchronized void fun2(a){ doSomething(); }}Copy the code

After thread 1 acquires the lock of thread A, if it is A non-reentrant lock, it will need to wait for the lock to be acquired by thread 2. However, if it cannot obtain the lock, then fun1 will not be able to release the lock, which may cause A deadlock.

The implementation of a reentrant lock is that each time a lock is added, the thread ID will be compared to whether the current thread holding the lock is the same. If so, the lock will not be acquired.

In ReentrantLock, state is incremented by one if it is a ReentrantLock.

6. Shared and exclusive locks

ReentrantReadWriteLock Read/write lock.

Write lock: Reentrant exclusive lock.

Read lock: a reentrant shared lock.

Lock degradation: Write locks can be degraded to read locks. This piece is not too understanding, the feeling is very difficult to understand, and do not know in what scene to use, there is a big guy who knows can give advice.

7. Similarities and differences between synchronized and ReentrantLock

  • A ReentrantLock is an interruptible lock that can be waived by a waiting thread to do something else if the wait time is long

  • Both ReentrantLock and synchronized are unfair locks, but ReentrantLock can be set to fair through constructors.

  • ReentrantLock can have multiple conditions set. Synchronized locks maintain a wait queue, and reentrantLock conditional waits maintain a queue for each condition, so that conditional wake-ups can be implemented.

8. Implementation mechanism of ReentrantLock — AQS