Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Concurrent package is based on the AQS (AbstractQueuedSynchronizer) framework, AQS framework by means of two classes:

Therefore, LockSupport is very important.

 LockSupport introduction

LockSupport is a lower-level class in the JDK that creates the basic thread-blocking primitive for locks and other synchronization utility classes. Java and synchronizer frame lock core AQS: AbstractQueuedSynchronizer, is by calling LockSupport. The park () and LockSupport unpark () threads blocked and unblocked. The functions of park() and unpark() in LockSupport are to block and unblock threads, respectively, and park() and unpark() do not encounter “deadlocks that may be caused by Thread.suspend and thread.resume” problems.

The LockSupport class is a class introduced in Java6(JSR166-JUc) that provides basic thread synchronization primitives. LockSupport actually calls the functions in the Unsafe class. In broadening, there are only two functions:

  • If the current thread is blocked, the parking space will be occupied. It’s a pretty good name.
  • Unpark: Unblock the given thread blocked.

Because park() and unpark() have permissions; The race between the thread calling park() and another thread trying to unpark() it will remain active.

Class diagram:

List of LockSupport functions

// Private static void setBlocker(Thread t, Object arg) // Returns the Blocker provided to the last unblocked call to the park method, or null if the call is not blocked. Static Object getBlocker(Thread t) // Disables the current Thread for Thread scheduling, unless permission is available. Static void park() // For thread scheduling, disable the current thread until permission is available. Static void park(Object Blocker) // Disables the current thread for thread scheduling, waiting up to the specified wait time, unless permission is available. Static void parkNanos(long Nanos) // For thread scheduling, disable the current thread before permission is available and wait up to the specified wait time. Static void parkNanos(Object Blocker, Long Nanos) // Disables the current thread before the specified time limit for thread scheduling, unless permission is available. Static void parkUntil(long deadline) // For thread scheduling, disable the current thread before the specified time limit, unless permission is available. Static void parkUntil(Object Blocker, long deadline) // Make the permission for a given thread available if it is not already available. static void unpark(Thread thread)Copy the code

Two key

(1) Operation object

After all, the native code in the Unsafe call to LockSupport:

public static void unpark(Thread thread) { if (thread ! = null) UNSAFE.unpark(thread); } public static void park() { UNSAFE.park(false, 0L); }Copy the code

The park function blocks the current Thread, and the unpark function wakes up the specified Thread.

Compared with the wait/notify mechanism of Object, park/unpark has two advantages:

  1. Using thread as the operation object is more consistent with the intuitive definition of blocking threads.

  2. The operation is more precise and can wake up a thread exactly (notify randomly wakes up one thread, notifyAll wakes up all waiting threads), increasing flexibility.

(2) Licensing

In the above text, I used blocking and wake to contrast with wait/notify.

  • In fact, the core design principle of Park/Unpark is “permission”. Park is waiting for a permit. Unpark provides a license for a thread. If thread A calls park, thread A will block on the park operation unless another thread calls unpark(A) to give A permission.
  • One thing that is difficult to understand is that the unpark operation can precede the park operation. In other words, give permission first. When a thread calls Park and already has permission, it consumes the permission and can continue running. It’s a must. Consider the simplest model: the Producer needs to consume a resource and calls the Park operation to wait. The Producer produces the resource and then calls unpark to give the Consumer permission to use it. It is quite possible that the Producer produces first, and the Consumer may not have been constructed (for example, the thread hasn’t been started or switched to that thread). So when the Consumer is ready to consume, obviously the resource has been produced and can be used directly, then of course the park operation can be run directly. Without this semantics, it would be very difficult to manipulate.
  • But this “license” is not additive, “license” is one-time. For example, if thread B calls unpark three times in A row, this “permission” will be used when thread A calls park. If thread A calls park again, it will enter the wait state.

Flexibility of Park and Unpark

As mentioned above, the unpark functions can be called before park, which is where their flexibility lies.

A thread may call park before, after, or at the same time as another thread unPark. Because of the nature of Park, it does not have to worry about the timing of its park. Otherwise, if park has to be before unPark, it will cause a lot of programming trouble!

Let’s think about what happens when two threads synchronize?

In Java5, synchronization is done with wait/notify/notifyAll. One of the annoying aspects of wait/notify is that if thread B wants to notify thread A, thread B must ensure that thread A has waited on the wait call, otherwise thread A may wait forever. It’s a pain in the ass when you’re programming.

Also, is it notify or notifyAll?

Notify will only wake up one thread, and if there are two threads waiting on the same object by mistake, then again. It seems that notifyAll is the only way to be safe. Unpark can be specified to a particular thread.

The Park/Unpark model really decouples synchronization between threads. Threads no longer need an Object or other variable to store state and no longer need to care about each other’s state.

 

The underlying implementation principles for unsafe. park and unpark

In Linux system, it is implemented by MUtex (mutex) and condition (condition variable) in Posix thread library PThread. Mutex and condition protect a _counter variable that is set to 0 when park and 1 when unpark.

Each Java thread has a Parker instance. The Parker class is defined like this:

class Parker : public os::PlatformParker { private: volatile int _counter ; . public: void park(bool isAbsolute, jlong time); void unpark(); . } class PlatformParker : public CHeapObj<mtInternal> { protected: pthread_mutex_t _mutex [1] ; pthread_cond_t _cond [1] ; . }Copy the code

You can see that the Parker class is actually implemented using Posix’s mutex, condition. The _counter field in the Parker class is used to record “permission”.

  • Park process

When calling park, first try to get permission directly, that is, _counter>0. If successful, set _counter to 0 and return:

void Parker::park(bool isAbsolute, jlong time) { // Ideally we'd do something useful while spinning, such // as calling unpackTime(). // Optional fast-path check: // Return immediately if a permit is available. // We depend on Atomic::xchg() having full barrier semantics // since we  are doing a lock-free update to _counter. if (Atomic::xchg(0, &_counter) > 0) return;Copy the code

If not, construct a ThreadBlockInVM and check if _counter is >0. If so, set _counter to 0, unlock mutex and return:

ThreadBlockInVM tbivm(jt);  
if (_counter > 0)  { // no wait needed  
  _counter = 0;  
  status = pthread_mutex_unlock(_mutex);  
Copy the code

If not, call pthread_cond_wait and wait. If wait returns, set _counter to 0, unlock mutex and return:

if (time == 0) {  
  status = pthread_cond_wait (_cond, _mutex) ;  
}  
_counter = 0 ;  
status = pthread_mutex_unlock(_mutex) ;  
assert_status(status == 0, status, "invariant") ;  
OrderAccess::fence();  
Copy the code
  • Unpark process

When unpark, it’s much easier, just set _counter to 1, and unlock mutex returns. If the value before _counter is 0, the pthread_cond_signal is also called to wake up the threads waiting in the park:

void Parker::unpark() { int s, status ; status = pthread_mutex_lock(_mutex); assert (status == 0, "invariant") ; s = _counter; _counter = 1; if (s < 1) { if (WorkAroundNPTLTimedWaitHang) { status = pthread_cond_signal (_cond) ; assert (status == 0, "invariant") ; status = pthread_mutex_unlock(_mutex); assert (status == 0, "invariant") ; } else { status = pthread_mutex_unlock(_mutex); assert (status == 0, "invariant") ; status = pthread_cond_signal (_cond) ; assert (status == 0, "invariant") ; } } else { pthread_mutex_unlock(_mutex); assert (status == 0, "invariant") ; }}Copy the code

LockSupport sample

You can see the usage of LockSupport more clearly by comparing Example 1 and Example 2.

Example 1

package lock.demo7; public class WaitTest1 { public static void main(String[] args) { ThreadA ta = new ThreadA("ta"); Try {system.out.println (thread.currentThread ().getName() +" start ta"); ta.start(); System.out.println(Thread.currentThread().getName() + " block"); // The main thread waits ta.wait(); System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } static class ThreadA extends Thread { public ThreadA(String name) { super(name); } public void run() {synchronized(this) {// Synchronized (this) System.out.println(Thread.currentThread().getName() + " wakup others"); notify(); // Wake up the waiting thread on the current object}}}}Copy the code

Example 2

package lock.demo8; import java.util.concurrent.locks.LockSupport; public class LockSupportTest1 { private static Thread mainThread; public static void main(String[] args) { ThreadA ta = new ThreadA("ta"); MainThread = thread.currentThread (); System.out.println(Thread.currentThread().getName() + " start ta"); ta.start(); System.out.println(Thread.currentThread().getName() + " block"); // mainThread blocks locksupport. park(mainThread); System.out.println(Thread.currentThread().getName() + " continue"); } static class ThreadA extends Thread { public ThreadA(String name) { super(name); } public void run() { System.out.println(Thread.currentThread().getName() + " wakup others"); // wake up the "mainThread" locksupport. unpark(mainThread); }}}Copy the code

Running results:

main start ta
main block
ta wakup others
main continue
Copy the code

Difference between park and wait Wait allows a thread to acquire a lock through synchronized before blocking.

 

Example 3

LockSupport is similar to a binary semaphore (only one license is available). If the license is not already occupied, the current thread acquires the license and executes it. If the license is already occupied, the current line is blocked, waiting for the license.

public static void main(String[] args)
{
     LockSupport.park();
     System.out.println("block.");
}
Copy the code

Running this code, you can see that the main thread is always blocked. Because permissions are occupied by default, a call to park() does not get permissions, so it is blocked.

The main thread can terminate normally if the license is released and then obtained. LockSupport permissions are obtained and released in a corresponding way. If you unpark multiple times, there will be no problem with only one park. The result is that the permissions are available.

public static void main(String[] args) { Thread thread = Thread.currentThread(); LockSupport.unpark(thread); // Release license locksupport.park (); System.out.println("b"); }Copy the code

LockSupport is non-reentrant, and if a thread calls locksupport.park () twice in a row, the thread will block forever.

public static void main(String[] args) throws Exception
{
    Thread thread = Thread.currentThread();
    
    LockSupport.unpark(thread);
    
    System.out.println("a");
    LockSupport.park();
    System.out.println("b");
    LockSupport.park();
    System.out.println("c");
}
Copy the code

This code prints a and B, not C, because a second call to park causes the thread to deadlock because it can’t get permission.

Let’s look at the responsiveness of LockSupport to interrupts

public static void t2() throws Exception { Thread t = new Thread(new Runnable() { private int count = 0; @Override public void run() { long start = System.currentTimeMillis(); long end = 0; while ((end - start) <= 1000) { count++; end = System.currentTimeMillis(); } System.out.println("after 1 second.count=" + count); // Wait for maybe permission locksupport.park (); System.out.println("thread over." + Thread.currentThread().isInterrupted()); }}); t.start(); Thread.sleep(2000); // Interrupt thread T.innterrupt (); System.out.println("main over"); }Copy the code

The final thread prints thread over. True. This means that if the thread is blocked by calling park, it can respond to interrupt requests (the interrupt status is set to true) but does not throw InterruptedException.