Analysis from the ideological lock to lock mainstream Java

Optimistic and pessimistic locking

Optimistic and pessimistic locking is a concept, the main difference is in their attitude towards the time of thread synchronization.

  • Pessimistic locking that he's sure there are other threads to modify the data, so it will first add the data before using locks in the use of data, until the lock is released after use of resources. Java, synchronizedthe keywords and Lockimplementation classes are all pessimistic locking.
  • Conversely optimistic locking believes that there will be no threads to modify the data in the use of data, so it does not add lock, except when updating data to determine whether there is a thread to modify the data. If the data is not updated, the data is written to the success of the current thread. If the data is updated, the process will be different in different execution (retry or error) depending on the implementation. Java, the most common implementation is optimistic locking CAS atomic classes.

It is because of different optimistic and pessimistic locking, and they apply a natural scene is not the same,

  1. Optimistic locking suitable for reading and writing little scenes, no lock is designed to dramatically increase concurrency efficiency.
  2. Pessimistic lock is suitable for write once read many small scenes, before use locking to ensure data security.

use

//=============== 悲观锁 ===============
//synchronized
public synchronized void test() {
    //需要同步的资源
}

/**
 * ReentrantLock
 * 需要保证多线程操作的是同一个锁
 */
ReentrantLock lock = new ReentrantLock();
public void test1() {
    lock.lock();
    try {
        //需要同步的资源
    } finally {
        lock.unlock();
    }
}

//=============== 乐观锁 ===============
/**
 * 需要保证多线程操作的是同一个AtomicInteger
 */
AtomicInteger atomicInteger = new AtomicInteger(0);
public void test2() {
    atomicInteger.getAndIncrement();
}
复制代码

By using the above-mentioned manner we can conclude, pessimistic locking is to acquire a lock to synchronize data by explicitly calling, but why do not show acquire optimistic locking lock can also synchronize data yet. Here we must talk about what is the CAS.

CAS (compare and swap)

Literally speaking, that is to compare and exchange, is a lock-free algorithms. That can be locked without the need to realize multi-thread synchronization variables. In Java, the atomicclass of the Atoms in the package is a series of CAS realization

Among them, we have the most common AtomicInteger analysis, source code is as follows,

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            //反射获取AtomicInteger类中value值的偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    //通过volatile关键字防止cpu指令重排序
    //使value对所有线程可见
    private volatile int value;
    
    ...
    
    public final int getAndIncrement() {
        //实际调用的是Unsafe.getAndAddInt
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
}

//Unsafe类
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    //通过循环重试比较新值与旧值,直到两者相等说明此时数据未被其他线程修改,之后更新内存中的变量值
    do {
        var5 = this.getIntVolatile(var1, var2);
    //compareAndSwapInt这个方法是native方法具体分析见底下
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

//native方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
复制代码

CAS visible actual implementation is operating in the native layer compareAndSwapInt()in, by means of CPU instructions in the JNI cmpxchgto complete, the instruction is an atomic operation. Obviously, it is possible to ensure the visibility of variables.

Specifically the CPU cmpxchgwhat to do instruction is the value of the compare register A and memory V.

  1. If they are equal, the new value B to be written into memory.
  2. If not equal, the value of the memory assigned to the value of V in register A.

Following the adoption of the above-mentioned do-whilecalling the cycle again cmpxchginstruction retry the update until it succeeds.

CAS problems caused by

CAS Although this algorithm is very efficient, but there are problems.

  1. ABA problem because CAS before updating variables need to check whether the variable can be updated at this time if the variable A is updated to B and then immediately updated to A. Then clearly there is a case lead to CAS considered variables did not change, but the reality is there is a change (thread-safe strategy becomes unreliable). Solution of each variable can be updated recording a version number, namely 1A-2B-3A, so do compare CAS will not appear when he was mistaken for a variable has been updated without updating the.
  2. Rotation Strategy cause high CPU overhead.

Fair and unfair lock lock

  • Fair locks, in order to apply the thread lock to hold the lock. The advantage is waiting threads will not hunger, but the drawback is decline in throughput efficiency than non-fair locks. In addition to acquiring the lock thread remaining thread is blocked, with large CPU overhead of thread wakes up to do.
  • Unfair lock, the thread acquires the lock is disordered, the presence of the thread to jump the queue to obtain the lock. The advantage of high throughput efficiency, because the thread has a chance not to get blocked on a lock, but the drawback may cause the thread has been waiting, hungry.

ReentrantLock fairness locking and non-locking fair

public class ReentrantLock implements Lock, java.io.Serializable {
    ...
    public ReentrantLock() {
        //可见ReentrantLock默认使用的是非公平锁
        sync = new NonfairSync();
    }
    ...
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    //非公平锁的实现
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
    
        final void lock() {
            ...
        }
    
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
    //公平锁的实现
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            ...
        }
        
        protected final boolean tryAcquire(int acquires) {
            ...
        }
    }
}
复制代码

We observed a logical lock is actually acquired in the tryAcquireprocess, we NonfairSyncand FairSyncthe (left FairSync) which do horizontal comparison to see what the difference between them,

In addition to increasing the hasQueuedPredecessorsoutside is no different,

public final boolean hasQueuedPredecessors() {
    ...
    Node t = tail;
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
复制代码

The method is mainly determined whether the current thread is in the first synchronous queue . If it returns true, otherwise false.

Reentrant and non-reentrant lock latch

  • After reentrant lock, lock acquired in the outer method, if the method is called internally the need to acquire the lock, it will automatically get (must be the same lock). It does not get to the outer method because the lock is not released out is blocked. Reentrant lock feature is that deadlocks can be avoided to some extent.
  • Similarly the non-reentrant lock does not allow the above-described circumstances, such as recursive do not use it.

Exclusive locks and shared locks

  • Also known as exclusive lock mutex. The lock can only be held by a thread, the thread acquires the lock can read and write at the same time. In Java synchronizedand Lockimplementation classes belong to the mutex.
  • Shared lock, the lock can be held by multiple threads, if a thread is added to the variable A shared lock, then thread after only shared locks. And thread to acquire a shared lock can only write, can not read.

In Java ReentrantReadWriteLockclass implements the mutex and shared locks, as follows

ReentrantReadWriteLockThere are two locks, ReadLock read lock is a shared lock, WriteLock write lock is a mutex.

Guess you like

Origin juejin.im/post/5e2c0ecd51882526163501ad
Recommended