Java lock type

There are several classes of Java locks.

Optimistic locks and pessimistic locks

  • Optimistic locking is when the JVM believes that concurrency can be guaranteed without locking. A typical implementation is one such as AtomicInteger.

  • Pessimistic locks are locks that require mutual exclusion. Typical implementations are Synchronized and ReentrantLock.

Reentrant and non-reentrant

  • Reentrant means that when a thread acquires a lock but does not release it, the thread attempts to acquire the lock again and still succeeds. Synchronized and ReentrantLock are reentrant locks.
  • Non-reentrant is reentrant, so it will deadlock itself. There should be no such implementation.

Fair locks and unfair locks

  • Fair locking means that the thread that first requests the lock must acquire the lock first, also known as a FIFO. Is it fair or is it reasonable? Probably not, as this would cause context switching. ReentrantLock is a non-fair lock by default, but a fair lock instance can be constructed using a constructor.
  • An unfair lock is when the new thread has the first chance to get the lock, that is, to jump the queue. Reasonable? It may also be unreasonable, as this can lead to “starvation” : old threads in the queue never get the lock. Sysnchronized is an unfair lock.

Exclusive and shared locks

  • An exclusive lock is a lock acquired by one thread that no other thread can acquire. In most cases, exclusive locks are used.
  • A shared lock means that multiple threads can acquire the lock at the same time. It is common for multiple threads to acquire read locks at the same time.

Second, the synchronized

Synchronized fundamentals are implemented through CPU instructions. Before jdk1.6, locks were heavy. Because Java multithreading corresponds to operating system threads one to one. When the Java thread blocks, it needs to switch to the kernel mode to block, and when it wakes up, it needs to switch from the kernel mode to the user mode, making a heavy context switch. Can a thread not block if it can’t get a lock? Is spin ok? This brings up the four implementations of synchronized: no lock, bias lock, lightweight lock, and weight lock.

Synchronized locks Java object headers and, more specifically, Mark Words.

unlocked

There’s nothing to say about that. This object is not included through synchronized.

Biased locking

When only one thread accesses the lock, the threadId of the current thread is set in Mark Word via CAS. If successful, the lock is successful (since there is only one thread, it must be). When the thread requests the lock again, check whether the mark Word thread ID is the same as its own. If the same thread is locked successfully. Note that it does not unlock. If another thread also arrives, the CAS of the new thread will fail because the previous thread did not unlock the CAS. When the JVM has no bytecode to execute (the global safe point), it checks to see if the previous thread has terminated, and if so, it updates the Thread ID field in the Mark Word with CAS to the threadId of the new thread. If the last thread did not terminate, there is concurrency. Biased locks cannot fulfill their mission and need to be upgraded to lightweight locks.

Lightweight lock

Following the example above of the previous thread A biased towards the lock and the new thread B. The JVM now performs A mark word operation on thread A. Copy the mark word pointer to the current thread’s stack space, and then add A pointer to the mark Word pointer to the current thread’s stack space. Upgraded to lightweight lock. Thread B spins and waits for thread A to release. How does thread A release the lock? When the pointer to the first CAS operation (the mark word pointer to the thread stack) is released, thread B spins to detect the mark word pointer to preempt the lock. What if there’s another thread C? Is it also spinning? How many threads can spin at the same time? How many times can thread B spin? These are configurable with JVM parameters.

Weight of the lock

There’s really nothing to say about this. In case of concurrent access, switch the thread directly to kernel-mode blocking.

Third, already

Already by AQS (AbstractQueuedSynchronizer). Problems to be solved:

  • There needs to be a state that indicates whether the lock object was preempted and, if reentrant, how many times it was preempted by the thread. This state identifier is actually the state member variable of AQS. Operations on state must be thread-safe. You can solve the problem through CAS.
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); If (c == 0) {// This is a fair lock implementation. Preempt 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; }Copy the code
  • If multiple threads preempt the lock at the same time, only one thread can succeed. How can the other threads queue? How do queued threads preempt locks? So we’re using a queue. The insertion of this queue is achieved through spin and CAS.
private Node addWaiter(Node mode) { Node node = new Node(mode); // loop to try for (;;) { Node oldTail = tail; if (oldTail ! = null) {node.setPrevrelaxed (oldTail); // CAS modify tail if (compareAndSetTail(oldTail, node)) {// modify subsequent pointer oldtail.next = node; return node; } } else { initializeSyncQueue(); }}}Copy the code
  • What about queuing threads preempting a lock?
final boolean acquireQueued(final Node node, int arg) { boolean interrupted = false; try { for (;;) { final Node p = node.predecessor(); // If the first node is the head node, and the lock was acquired successfully, return directly. If (p == head && tryAcquire(arg)) {setHead(node); if (p == head && tryAcquire(arg)) {setHead(node); p.next = null; // help GC return interrupted; } / / whether need to block the if (shouldParkAfterFailedAcquire (p, node)) / / blocked here interrupted | = parkAndCheckInterrupt (); } } catch (Throwable t) { cancelAcquire(node); if (interrupted) selfInterrupt(); throw t; }}Copy the code
  • How do I wake up a blocked thread? This depends on the release logic.
Public final Boolean release(int arg) {// Release lock if (tryRelease(arg)) {Node h = head; if (h ! = null && h.waitStatus ! = 0) unparkSuccessor(h); // Wake up subsequent nodes of the header. Return true; } return false; }Copy the code

See meituan’s article for details.

Four, reference

  • The unspoken Java “lock” thing
  • Summary of Java Synchronized principle
  • See AQS principle and application from the realization of ReentrantLock

Hard advertising

Welcome to the public account: Double6