Java since version 5, in Java. Util. Concurrent. The locks package provides us except the synchronized keyword several new lock function implementation, already is one of them. But that doesn’t mean we can ditch the synchronized keyword.

Before these locks were implemented, the only way to implement locks was through the synchronized keyword, as shown in the following example:

private static volatile int value; public static void main() { Runnable run = new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { increaseBySync(); }}}; Thread t1 = new Thread(run); Thread t2 = new Thread(run); t1.start(); t2.start(); while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println(value); } private static synchronized int increaseBySync() { return value++; }Copy the code

With ReentrantLock, we can write:

private static volatile int value; private static Lock lock = new ReentrantLock(); public static void main(String[] args) { testIncreaseWithLock(); } public static void main() { Runnable run = new Runnable() { @Override public void run() { try { lock.lock(); for (int i = 0; i < 1000; i++) { value++; } } finally { lock.unlock(); }}}; Thread t1 = new Thread(run); Thread t2 = new Thread(run); t1.start(); t2.start(); while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println(value); }Copy the code

In both cases, the value is incremented by 1 and the correct value is 2000. Let’s take a look at what ReentrantLock does and how it works.

Reentrant lock

As the name suggests, ReentrantLock is a ReentrantLock. A reentrant lock means that when a thread has acquired the lock, it can be immediately acquired again by calling the lock() method.

For example, when a thread executes methodA(), assuming it has already acquired the lock, it can immediately acquire the lock inside methodB when it executes methodB(), since both methods are calling the same lock.

private static Lock lock = new ReentrantLock(); public static void methodA(){ try{ lock.lock(); //dosomething methodB(); }finally{ lock.unlock(); } } public static void methodB(){ try{ lock.lock(); //dosomthing }finally{ lock.unlock(); }}Copy the code

Synchronized is also a reentrant lock, so you can write your own example to test it.

Fair lock

ReentrantLock supports fair locking and is not fair by default.

Public ReentrantLock() {sync = new NonfairSync(); } public ReentrantLock(Boolean fair) {sync = fair? new FairSync() : new NonfairSync(); }Copy the code

We can see from the two lock() methods that the difference is that when the lock() method of the non-fair lock is called, the current thread will try to acquire the lock. If the acquisition fails, the acquire(1) method will be called to queue and wait. The lock() method that calls a fair lock will queue the current thread directly (acquire methods involving AQS will be discussed below).

/ / already source, Void lock() {if (compareAndSetState(0, 1)) setExclusiveOwnerThread(thread.currentThread ()); else acquire(1); } final void lock() {acquire(1); }Copy the code

Synchronized is an unfair lock.

Timeout mechanism

ReentrantLock also provides a timeout mechanism. When the tryLock() method is called, the current thread will return immediately if it fails to acquire the lock. When the tryLock() method is called, the current thread will return immediately if it has not acquired the lock within the specified timeout period. These functions are mainly realized by AQS.

//ReentrantLock source code public Boolean tryLock() {return sync.nonfairtryacquire (1); } //ReentrantLock source code public Boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }Copy the code

Synchronized doesn’t do that.

Can be interrupted by

ReentrantLock has a lockInterruptibly() method that ultimately calls both AQS methods:

AQS method 1 throws InterruptedException if the current thread is interrupted, otherwise an attempt to acquire the lock is returned on success, and doAcquireInterruptibly() is called on failure.

AQS method 2 also determines whether the current thread is marked as interrupted in the thread spin of the for loop and throws InterruptedException if it is.

DoAcquireInterruptibly () is similar to the doAcquire() method, with the exception that it throws InterruptedException.

/ / lock method public void lockInterruptibly () throws InterruptedException {sync. AcquireInterruptibly (1); } public final void acquireInterruptibly(int arg) throws InterruptedException {if (thread.interrupted ()) throw  new InterruptedException(); if (! tryAcquire(arg)) doAcquireInterruptibly(arg); } // AQS Method two Private void doAcquireInterruptibly(int arg) throws InterruptedException {final Node Node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); }}Copy the code

AQS (AbstractQueueSynchronizer)

AQS is a queue-based synchronizer. It is an abstract class, which mainly provides the queuing and activation mechanism for multi-thread lock acquisition. ReentrantLock has two subclasses based on AQS, which respectively support fair and unfair locks.

Let’s use fair lock as an example to show how ReentrantLock relies on AQS for its functionality. The main source code and explanation involved in obtaining locks are as follows:

/ / AQS source, Public final void acquire(int arg) Public final void acquire(int arg) public final void acquire(int arg)  { if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 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; } final Boolean acquireQueued(final Node Node, int arg) {Boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); If (p == head && tryAcquire(arg)) {setHead(Node); if (p == head && tryAcquire(arg)) {setHead(Node); p.next = null; // help GC failed = false; return interrupted; } / / shouldParkAfterFailedAcquire method marks the current thread a prerequisite for the Node Node waitStatus for SIGNAL, The parkAndCheckInterrupt method suspends the current thread, stops spinning, No waste of CPU resources if (shouldParkAfterFailedAcquire (p, node) && parkAndCheckInterrupt ()) interrupted = true; } } finally { if (failed) cancelAcquire(node); }}Copy the code

Suppose there are threads A, B, and C trying to acquire the same lock at the same time. The general flow is as follows. Suppose thread A obtains the lock and returns it successfully, and threads B and C call the above methods in turn to enter the AQS waiting queue and finally be suspended.

Now that thread A has done its job, it calls unlock in the finally block. Let’s look at the source code.

// The current thread will determine if its Node's waitStatus is marked by its successor when it releases the lock. Public final Boolean release(int arg) {if (tryRelease(arg)) {Node h = head; if (h ! = null && h.waitStatus ! = 0) unparkSuccessor(h); return true; } return false; } //AQS source code, focus on the bottom locksupport.unpark (s.read), Private void unparksucceeded (Node Node) {/* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t ! = null && t ! = node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s ! = null) LockSupport.unpark(s.thread); }Copy the code

When thread A releases the lock, it wakes up the head’s successor, thread B, and thread B can continue spinning in the for loop and successfully acquire the lock.

The unsafe related

The Unsafe object was mentioned earlier in the presentation of AtomicInteger, which uses the CAS functionality of the Unsafe object (underlying CPU functionality).

ReentrantLock, in addition to CAS, also uses the Unsafe pack and unpack methods (LockSupport), which, in addition to its enhanced performance, can accurately wake up a thread.

Demo code location


SRC/main/Java/net/weichitech/juc/ReentrantLockTest. Java, small western/Java programming – learning – Gitee.com

Related articles

JAVA concurrency AtomicInteger principle analysis