[multithreading] what are pessimistic locks and optimistic locks

Posted by Christam1 on Thu, 03 Feb 2022 09:53:06 +0100

Pessimistic lock

concept

Pessimistic lock considers the result in the worst-case plan. It needs to lock it at the same time of each resource operation to avoid preemption by other threads. I absolutely guarantee that there will be no problem with my execution this time.

Applicable scenario

Pessimistic locking is applicable to competitive incentive scenarios, such as highly concurrent read and write operations.

classic case

synchronized keyword

public class TestLock implements Runnable{

    private static Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread th01 = new Thread(new TestLock());
        Thread th02 = new Thread(new TestLock());
        th01.start();
        th02.start();
    }

    @Override
    public void run() {
        synchronized (object){
            System.out.println(Thread.currentThread().getName()+"Lock acquired");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"The lock was released");
    }
}

Output results

Thread-0 Lock acquired
Thread-0 The lock was released
Thread-1 Lock acquired
Thread-1 The lock was released

Can see
When Thread-0 obtains the lock, Thread-1 will fall into the blocking state. Because synchronized plays a pessimistic lock at this time, other threads cannot obtain the lock state while Thread-0 enters the lock

Lock interface

public class TestLock implements Runnable{

    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread th01 = new Thread(new TestLock());
        Thread th02 = new Thread(new TestLock());
        th01.start();
        th02.start();
    }

    @Override
    public void run() {
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"Lock acquired");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.unlock();
        System.out.println(Thread.currentThread().getName()+"The lock was released");
    }
}

The functions of Lock and synchronized are the same. They are pessimistic locks, but Lock has more advantages than
synchronized is strong. At ordinary times, we need to implement a pessimistic Lock. It is recommended to use Lock. I won't elaborate too much on the specific advantages here. I'll talk about this article later.

Optimistic lock

concept

The optimistic lock is not actually locked in the Java thread. Instead, it judges whether the current thread has been operated by other threads by judging the initial value. When the optimistic lock judges that the initial value is consistent with the initial value started at that time after this execution, it means that the thread is executing this time, The modified value has not been modified by other threads. If it is inconsistent, it is modified.

Applicable scenario

Optimistic locking is applicable to the case where there are many reads but few writes, and the concurrency is not very high.

classic case

Atomic class

AtomicInteger

public class TestLock{

    private static AtomicInteger integer = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread th01 = new Thread(()->{
            while (integer.get()<10000){
                System.out.println(Thread.currentThread().getName()+"output"+integer.addAndGet(1));
            }
        });
        Thread th02 = new Thread(()->{
            while (integer.get()<10000){
                System.out.println(Thread.currentThread().getName()+"output"+integer.addAndGet(1));
            }
        });
        th01.start();
        th02.start();
        th01.join();
        th02.join();
        System.out.println(integer.get());
    }
}

In fact, the above code simply implements an operation similar to i + +, but why do you need it
AtomicInteger instead of i + +, isn't it easier and faster to use i +? In fact, the reason for using AtomicInteger here is that i + + is not an atomic operation, and it will be added less in a multi-threaded environment. However, AtomicInteger is different. It implements optimistic locking based on CAS algorithm and ensures that it will reverse the value it wants every time i +, Next, let's take a look at the implementation process of AtomicInteger + + i.

In the code of AtomicInteger, the above process is realized by calling the compareAndSwapInt method. This method uses a comparison and swap mechanism. In this method, there are several parameters:

var1:afferent AtomicInteger object
var2:AtomicInteger Offset address of variable in
var5:Modify previous AtomicInteger Value in
var5+var4:Expected results

When compareAndSwapInt starts to execute, it will first obtain the correct value saved in the memory of AtomicInteger according to the passed in AtomicInteger object and the offset address of variables in AtomicInteger, and then compare it with the AtomicInteger value obtained before modification. If it is consistent, start to execute var5+var4 to obtain the expected result. If it is inconsistent, it will start again.

++For example, let's explain the above process again in words++
Step 1: the initial AtomicInteger value is 0
Step 2: thread A starts execution and obtains the value of AtomicInteger as 0;
Step 3: thread A suspends execution, and thread B starts to obtain the value of AtomicInteger as 0;
Step 4: thread B starts executing ++
i operation, obtained the value of 1, and modified the value of AtomicInteger
Step 5: after the execution of thread B, switch to thread A.
Step 6: thread A starts to execute + + i operation. At this time, thread A will get the value of AtomicInteger again. It is found that the value of AtomicInteger is 1, which is different from the value of AtomicInteger obtained before modification. If the comparison fails, return false and continue the cycle. The execution is successful until the value of AtomicInteger to be executed next time is equal to the value before modification.

Topics: Java