Optimistic lock and pessimistic lock

Posted by viperfunk on Tue, 01 Feb 2022 22:23:39 +0100

1. Optimistic lock

Optimistic lock assumes that the data will not generate concurrency conflict under normal circumstances, so when the data is submitted and updated, it will formally detect whether the data has concurrency conflict. If concurrency conflict is found, it will return the user's error information and let the user decide how to do it.

2. Implementation of optimistic lock

CAS algorithm

3.CAS algorithm

3.1CAS definition

CAS(compare and swap): compare and replace, which is used to manage shared access to concurrent data

We assume that the original data V in memory, the old expected value A, and the new value B that needs to be modified.

  • Compare whether A and V are equal. (comparison)
  • If the comparisons are equal, write B to V. (exchange)
  • Returns whether the operation was successful.

3.2CAS implementation principle

In Java, the method under the unsafe class is used at the bottom of CAS

The native modification is added before the compare and swap method, which shows that the implementation of this method is not implemented in java language, but in c/c + + language

Summary:

  • java CAS uses the CAS operations provided by the unsafe class;
  • unsafe CAS relies on Atomic::cmpxchg implemented by jvm for different operating systems;
  • The implementation of Atomic::cmpxchg uses the compiled CAS operation and uses the lock mechanism provided by cpu hardware to ensure its atomicity.

3.3CAS application in java

Implementing i + + in multithreading, i – ways to ensure thread safety:
1. Lock
2.ThreadLocal
3.AtomicInteger

AtomicInteger/Atomic example:

package thread_6_10;


import java.util.concurrent.atomic.AtomicInteger;

public class Demo01 {

    public static int count = 0;
    public static final int MAX_SIZE = 100000;
    static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < MAX_SIZE; i++) {
                    atomicInteger.getAndIncrement();
                }
            }
        });

        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < MAX_SIZE; i++) {
                    atomicInteger.getAndDecrement();
                }
            }
        });

        t2.start();

        Thread.sleep(100);
        System.out.println("Final result:"+atomicInteger);
    }
}

Operation results:

Final result: 0

Disadvantages of CAS algorithm

Although CAS algorithm efficiently solves atomic operations, CAS still has three major problems: ABA problem, long cycle time, high overhead and atomic operations that can only guarantee one shared variable

4.1ABA issues

4.1.1 what is ABA

The problem with ABA is that A value changes from A to B and then to A, and we don't know the process during this period.

If the value of A variable read for the first time is A, and it is checked that it is still A value when preparing the assignment, can we say that its value has not been modified by other threads? Obviously not, because its value may be changed to other values during this time, and then changed back to A, then the CAS operation will mistakenly think that it has never been modified. This problem is called the "ABA" problem of CAS operation.

4.1.2 ABA problem solution

Add version number
From jdk1 Starting from 5, the Atomic package of JDK provides a class AtomicStampedReference to solve the ABA problem. The compareAndSet method first checks whether the current reference is equal to the expected reference and whether the current flag is equal to the expected flag. If they are all equal, set the values of the reference and the flag to the given update value in an Atomic manner.

(1) No version number added

Code example:

package thread_6_10;

import java.util.concurrent.atomic.AtomicReference;

public class Demo02 {
    private static AtomicReference money = new AtomicReference(100);

    public static void main(String[] args) throws InterruptedException {
        //Transfer out 100 yuan
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean ret = money.compareAndSet(100,0);
                System.out.println("Thread 1 transfer result:"+ret);
            }
        });
        t1.start();

		//Transfer in 100 yuan
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean ret = money.compareAndSet(0,100);
                System.out.println("Thread 3 transfer result:"+ret);
            }
        });
        t3.start();

		//Transfer out 100 yuan
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean ret = money.compareAndSet(100,0);
                System.out.println("Thread 2 transfer result:"+ret);
            }
        });
        t2.start();

        t1.join();
        t3.join();
        t2.join();
    }
}

Operation results:

Thread 1 transfer result: true
 Thread 3 transfer result: true
 Thread 2 transfer result: true

(2) Add version number

Code example:

package thread_6_10;

import java.util.concurrent.atomic.AtomicStampedReference;

public class Demo03 {

    private static AtomicStampedReference money = new AtomicStampedReference(100,1);
    public static void main(String[] args) throws InterruptedException {

		//Transfer out 100 yuan
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean ret = money.compareAndSet(100,0,1,2);
                System.out.println("Thread 1 transfer result:"+ret);
            }
        });
        t1.start();

		//Transfer in 100 yuan
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean ret = money.compareAndSet(0,100,2,3);
                System.out.println("Thread 3 transfer result:"+ret);
            }
        });
        t3.start();

		//Transfer out 100 yuan
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean ret = money.compareAndSet(100,0,1,2);
                System.out.println("Thread 2 transfer result:"+ret);
            }
        });
        t2.start();

        t1.join();
        t3.join();
        t2.join();
    }
}

Operation results:

Thread 1 transfer result: true
 Thread 3 transfer result: true
 Thread 2 transfer result: false

matters needing attention:
Differences between AtomicReference and AtomicStampedReference:

  • There is an ABA problem with AtomicReference
  • The version number of AtomicStampedReference is added to solve the ABA problem

4.2 long cycle time and high cost

If spin CAS fails for a long time, it will bring very large execution overhead to the CPU. If the JVM can support the pause instruction provided by the processor, the efficiency will be improved to a certain extent. Pause instruction has two functions. First, it can delay the pipeline execution instruction (de pipeline), so that the CPU will not consume too much execution resources. The delay time depends on the specific implementation version. On some processors, the delay time is zero. Second, it can avoid the CPU pipeline flush ing caused by memory order violation when exiting the loop, so as to improve the execution efficiency of the CPU.

Solution:

  • Destroy the for loop. When it exceeds a certain time or a certain number of times, return to exit. The new LongAddr in JDK8 is similar to the method of ConcurrentHashMap. When multiple threads compete, the granularity becomes smaller. A variable is divided into multiple variables to achieve the effect that multiple threads access multiple resources. Finally, sum is called to combine it
  • If the JVM can support the pause instruction provided by the processor, the efficiency will be improved. Pause instruction has two functions: first, it can delay the pipeline execution instruction (de pipeline), so that the CPU will not consume too much execution resources. The delay time depends on the specific implementation version. On some processors, the delay time is zero; Second, it can avoid the CPU pipeline being emptied due to Memory Order Violation during the cycle, so as to improve the implementation efficiency of the CPU.

4.3 atomic operation of only one shared variable can be guaranteed

CAS is only valid for a single shared variable. CAS is invalid when the operation involves spanning multiple shared variables. However, starting from JDK 1.5, AtomicReference class is provided to ensure the atomicity between reference objects. You can put multiple variables in one object for CAS operation Therefore, we can use locks or use the AtomicReference class to combine multiple shared variables into a shared variable to operate.

Solution:

  • Lock
  • Encapsulate into objects. Note: starting from Java 1.5, JDK provides AtomicReference class to ensure atomicity before referencing objects. Multiple variables can be placed in one object for CAS operation.

5 pessimistic lock

That is very pessimistic. Every time you get the data, you feel that the data will be changed, so lock the record when you get the data, so that others can't change the data until your lock is released.

synchronized is an implementation of pessimistic locking

6. Implementation principle of synchronized:

Implementation principle: the JVM synchronizes methods and synchronization blocks by entering and exiting the object monitor, and the essence of the object monitor depends on the mutex lock of the underlying operating system.

The specific implementation is to add a monitor before the synchronous method call after compilation Enter instruction, insert monitor. At the exit method and exception Exit instruction. For the thread that does not acquire the lock, it will block the method entry until the thread that acquires the lock monitors Exit before attempting to continue acquiring the lock.

Flow chart Description:

Features of synchronized:

Topics: Multithreading Concurrent Programming