Java Concurrent Package Basic Element LockSupport

foreword

LockSupport and CAS are the basis of many concurrency tool control mechanisms in Java concurrent packages (AQS: AbstractQueuedSynchronizer, the core of the Lock and synchronizer framework), and they all rely on Unsafe implementation at the bottom.

LockSupport is the basic thread blocking primitive used to create locks and other synchronization classes. Its main core is to provide park() and unpark() methods to block and unblock threads.

Its basic principle is similar to a binary semaphore (only 1 license "permit" is available), when executing park(), if the unique license has not been occupied, the current thread obtains the unique license and continues to go. If the license is already occupied, the current thread is blocked, waiting for the license to be obtained. When unpark() is executed, the permission of the corresponding thread will be released.

 

Detailed explanation of park/unpark method

First look at the source code of the related methods in LockSupport (only the most basic park methods are listed here, other park methods with timeout parameters will not be introduced one by one)

public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
}

public static void park() {
        UNSAFE.park(false, 0L);
}

public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
}

Other variants of park methods with timeout will not be listed one by one.

From the source code, it can be found that the bottom layer of park/unpark is implemented by calling the unsafe class. For other park variants of park that increase the timeout time, it is also extended according to whether the first parameter of the unsafe park method is an absolute time. .

Regarding the C++ implementation of the park/unpark method in openJDK, it is mainly realized by using the mutex and condition of Posix. As for what is Posix, what is mutex, and condition, we will not explore in depth, as long as we know that this implementation is compatible with the platform Closely related, probably with the help of some implementation of the operating system. In short, it internally maintains a volatile modified int type _counter variable to record the so-called "permission". When parked, this variable is set to 0, and when unparked, this variable is set to 1.

 

It is worth noting that since the bottom layer of park calls the unsafe park implementation, if the park() method of LockSupport is called without immediate permission, after the current thread is blocked, the blocking will only be exited in the following situations status, which returns immediately:

1) Other threads have executed the unpark() method of the current thread . 2) Other threads interrupt the current thread . 3) If the timeout period of the park method is not 0, when the timeout period arrives . 4) Unreasonable false wakeup (that is, the legendary " Spurious wakeup ", similar to the wait() method of the Object class).

The first three situations are easy to understand, and the fourth situation seems a bit unacceptable. Why is there a situation where it is awakened for no reason? How does this ensure that our application does not have errors? Google has a lot of articles about Spurious wakeup, and there are probably the following explanations:

The first explanation: By analyzing the source code, it is found that the underlying pthread_cond_wait method is not placed in a while loop, but in an if judgment, so that when pthread_cond_wait is awakened, the conditional judgment will not be performed again, but will immediately return to the upper-level application . I don't think this can actually be called an explanation. It is at most the most superficial and superficial reason. More importantly, it should be why the pthread_cond_wait method returns when the condition is not established.

Second Explanation: This one says it's for performance reasons "Spurious wakeups may sound strange, but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations". But that also looks like an ambiguous explanation.

Third explanation: think this is a strategy of the operating system itself "Each blocking system call on Linux returns abruptly with  EINTR when the process receives a signal. ...  pthread_cond_wait() can't restart the waiting because it may miss a real wakeup in the little time it was outside the  futex system call.”

All in all, this kind of unreasonable false awakening exists, but the probability should be relatively small. We don’t need to delve into what the reason is. In response to this situation, how to ensure that our application is not affected by this false wake-up, the answer on the Internet is consistent, that is: put the call of the park() method in a code block that loops to check whether the condition is met:

while (<condition does not hold>)
     LockSupport.park();
    ... // perform an action that fits the condition

 

Detailed explanation of park/unpark features

1. The license is occupied by default , that is to say, if the park() is executed directly without executing unpark first, the license will not be obtained, so it will be blocked. An example is as follows:

public static void main(String[] args)
{
     LockSupport.park();//The license is already occupied by default, it will be blocked here
     System.out.println("block.");//This will not be executed
}

2. LockSupport is not reentrant, but unpark can be called multiple times. 

public static void main(String[] args)
{
     Thread thread = Thread.currentThread();
     LockSupport.unpark(thread);//Release permission
	 System.out.println("a");
	 LockSupport.unpark(thread);//It is also possible to release the license again.
	 System.out.println("b");
     LockSupport.park();// Get permission
     System.out.println("c");
	 LockSupport.park();//Not reentrant, causing blocking
     System.out.println("d");
}
 The above code will only print out: a,b,c. will not print out c. Because when the park is called for the second time, the thread cannot obtain permission and thus blocks.

 

3. Support is interrupted

public static void main(String[] args) 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("before park.count=" + count);

				LockSupport.park();//Blocked
				System.out.println("thread over." + Thread.currentThread().isInterrupted());

			}
		});

		t.start();

		Thread.sleep(5000);

		t.interrupt();

		System.out.println("main over");
}
When thread t executes LockSupport.park(), it is blocked because the license is occupied by default, but the main thread interrupts thread t after 5 seconds, causing LockSupport.park() to wake up and print out thread over.true. It can be seen that if the thread is blocked due to calling park, it can respond to the interrupt request (the interrupt status is set to true), but will not throw an InterruptedException.

4. Don't worry about losing the wake-up signal

In the blocking/wakeup mechanism of the wait/notify/notifyAll mode, we must consider the timing of notify and wait calls to avoid calling notify before the wait method call, which will result in missing the wake-up signal and make the application wait forever. The unpark() method of LockSupport can be executed before, after or even at the same time when the park() method is called, which can achieve the purpose of waking up the thread. Moreover, the implementation mechanism of park and Object.wait() is different, the blocking queues of the two do not intersect, and object.notifyAll() cannot wake up the thread blocked by LockSupport.park().

5. Facilitate thread monitoring and tool positioning 

There are getter and setter methods of parkBlocker in the LockSupport class. You can see that it obtains the value of the parkBlocker member attribute of the corresponding thread by unsafely using the offset address of the instance member attribute parkBlocker of the Thread class.

private static final long parkBlockerOffset;

static{
try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
        } catch (Exception ex) { throw new Error(ex); }

}
public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}

private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
}

 This parkBlocker object is used to record who blocked the thread when it was blocked. The blocked object can be obtained through the getBlocker of LockSupport. It is used for thread monitoring and analysis tools to locate the cause.

Guess you like

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