Reentrant lock - ReentrantLock

All source code of this blog comes from JDK 1.8

ReentrantLock, a reentrant lock, is a recursive non-blocking synchronization mechanism. It can be equivalent to the use of synchronized, but ReentrantLock provides a more powerful and flexible locking mechanism than synchronized, which can reduce the probability of deadlocks.

The API is described as follows:

A reentrant mutex lock Lock that has some of the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but is more powerful. The ReentrantLock will be owned by the thread that most recently successfully acquired the lock and has not released the lock. When the lock is not owned by another thread, the thread calling lock will successfully acquire the lock and return. If the current thread already owns the lock, this method returns immediately. You can use the isHeldByCurrentThread() and getHoldCount() methods to check if this happens.

ReentrantLock also provides a choice between fair locks and unfair locks. The constructor accepts an optional fair parameter ( default unfair lock ). When set to true, it means fair lock, otherwise it is unfair lock.

java.util.concurrent.locks.ReentrantLock.ReentrantLock()
java.util.concurrent.locks.ReentrantLock.ReentrantLock(boolean)

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

The difference between fair locks and unfair locks is that the lock acquisition of fair locks is sequential. However, the efficiency of fair locks is often not as high as that of unfair locks. In the case of many threads accessing, fair locks show lower throughput.

write picture description here

acquire lock

We generally use ReentrantLock to acquire locks like this:

//非公平锁
ReentrantLock lock = new ReentrantLock();
lock.lock();

lock method:

 public void lock() {
     sync.lock();
 }

java.util.concurrent.locks.ReentrantLock.Sync is an inner class in ReentrantLock, it inherits AQS (AbstractQueuedSynchronizer), it has two subclasses: fair lock FairSync and unfair lock NonfairSync.

Most of the functions in ReentrantLock are delegated to Sync. At the same time, Sync defines the lock() abstract method to be implemented by its subclasses. By default, the nonfairTryAcquire(int acquires) method is implemented. It can be seen that it is an unfair lock. Default implementation. Let's look at the lock() method of unfair locks:

java.util.concurrent.locks.ReentrantLock.NonfairSync.lock()方法

final void lock() {
     //尝试获取锁
     if (compareAndSetState(0, 1))
         setExclusiveOwnerThread(Thread.currentThread());
     else
         //获取失败,调用AQS的acquire(int arg)方法
         acquire(1);
 }

First, it will try to quickly acquire the lock for the first time. If the acquisition fails, the acquire(int arg) method will be called, which is defined in AQS as follows:

java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(int)方法

public final void acquire(int arg) {
     if (!tryAcquire(arg) &&
             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
 }

This method first calls the tryAcquire(int arg) method. As described in AQS, tryAcquire(int arg) needs to be implemented by a custom synchronization component. The implementation of the unfair lock is as follows:

protected final boolean tryAcquire(int acquires) {
  return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    //当前线程
    final Thread current = Thread.currentThread();
    //获取同步状态
    int c = getState();
    //state == 0,表示没有该锁处于空闲状态
    if (c == 0) {
        //获取锁成功,设置为当前线程所有
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //线程重入
    //判断锁持有的线程是否为当前线程
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

The main logic of this method: First, determine the synchronization state state == 0?, if it means that the lock is not held by the thread, obtain the synchronization state directly through CAS, and return true if successful. If state != 0, judge whether the current thread is the thread that acquires the lock, if so, acquire the lock, and return true if successful. The thread that successfully acquires the lock acquires the lock again, which increases the synchronization state state.

release lock

After acquiring the synchronization lock, you need to release the lock after use. ReentrantLock provides unlock to release the lock:

public void unlock() {
     sync.release(1);
 }

unlock uses Sync's release(int arg) internally to release the lock, and release(int arg) is defined in AQS:

public final boolean release(int arg) {
      if (tryRelease(arg)) {
          Node h = head;
          if (h != null && h.waitStatus != 0)
              unparkSuccessor(h);
          return true;
      }
      return false;
  }

Similar to the acquire(int arg) method for obtaining the synchronization state, the tryRelease(int arg) method for releasing the synchronization state also requires a custom synchronization component to implement it yourself:

protected final boolean tryRelease(int releases) {
    //减掉releases
    int c = getState() - releases;
    //如果释放的不是持有锁的线程,抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //state == 0 表示已经释放完全了,其他线程可以获取同步状态了
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

This method returns true only when the synchronization state is completely released. When state == 0, the lock holding thread is set to null, and free= true, indicating that the release is successful.

Fair lock and unfair lock

The difference between fair locks and unfair locks is whether the locks are acquired in the order of the FIFO . There is no fairness or unfairness in releasing locks. Taking unfair locks as an example above, let's take a look at tryAcquire(int arg) of fair locks:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

Comparing the process of acquiring the synchronization state between an unfair lock and a fair lock, you will find that the only difference between the two is that the fair lock has an additional restriction when acquiring the synchronization state: hasQueuedPredecessors(), which is defined as follows:

java.util.concurrent.locks.AbstractQueuedSynchronizer.hasQueuedPredecessors()方法

public final boolean hasQueuedPredecessors() {
    Node t = tail;  //尾节点
    Node h = head;  //头节点
    Node s;

    //头节点 != 尾节点
    //同步队列第一个节点不为null
    //当前线程是同步队列第一个节点
    return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
}

This method mainly does one thing: mainly to determine whether the current thread is the first in the CLH synchronization queue. Returns true if yes, false otherwise


Reference documents:
1. http://cmsblogs.com/?p=2210

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325484711&siteId=291194637