1. Unsafe class source code analysis
The Unsafe class in JDK's rt.jar package provides hardware-level atomic operations. The methods in Unsafe are all native methods, and local C++ implementation libraries are accessed by using JNI.
Explanation of the main functions of the Unsafe class in rt.jar. The Unsafe class provides hardware-level atomic operations and can safely operate memory variables directly. It is widely used in JUC source code. Understanding its principles lays the foundation for studying JUC source code.
First, let’s understand the use of the main methods in the Unsafe class, as follows:
1.long objectFieldOffset(Field field) method: Returns the memory offset address of the specified variable in the class to which it belongs. The offset address is only used when accessing the specified field in the Unsafe function. The following code uses unsafe to obtain the memory offset of the variable value in AtomicLong in AtomicLong object. The code is as follows:
static { try { valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }2.int arrayBaseOffset(Class arrayClass) method: get the address of the first element in the array
3.int arrayIndexScale(Class arrayClass) method: get the number of bytes occupied by a single element in the array
3.boolean compareAndSwapLong(Object obj, long offset, long expect, long update) method: compare whether the value of the variable of the offset offset in object obj is equal to expect. If it is equal, it is updated with the update value, and then returns true, otherwise it will return false.
4.public native long getLongVolative(Object obj, long offset) method: Get the value of the volatile memory semantics corresponding to the variable of the offset offset in object obj.
5.void putOrderedLong(Object obj, long offset, long value) Method: Set the value of the long field corresponding to the offset offset address in the obj object to value. This is the putLongVolatile method with delay, and does not guarantee that the value modification will be immediately visible to other threads. Variables are only useful if they are modified with volatile and are expected to be modified unexpectedly.
6.void park(boolean isAbsolute, long time) method: block the current thread. When the parameter isAbsolute is equal to false, time equal to 0 means blocking all the time. Time greater than 0 means that the blocking thread will be awakened after waiting for the specified time. This time is a relative value, an incremental value, that is, the current thread will be awakened after accumulating time relative to the current time. If isAbsolute is equal to true and time is greater than 0, it means that it will be awakened after blocking to the specified time point. Here time is an absolute time, which is the value converted to ms at a certain time point. In addition, when other threads call the interrupt method of the current blocking thread and interrupt the current thread, the current thread will also return. When other threads call the unpark method and take the current thread as a parameter, the current thread will also return.
7.void unpark(Object thread) method: Wake up the blocking thread after calling park, and the parameters are the threads that need to be woken up.
Several new methods have been added to JDK1.8. Here is a simple list of the methods for operating Long type as follows:
8.long getAndSetLong(Object obj, long offset, long update) Method: Get the value of the variable volatile semantics with offset in object obj, and set the value of the variable volatile semantics to update. The usage method is as follows:
public final long getAndSetLong(Object obj, long offset, long update) { long l; do { l = getLongVolatile(obj, offset);//(1) } while (!compareAndSwapLong(obj, offset, l, update)); return l; }From the internal code (1), you can use getLongVolative to get the value of the current variable, and then use CAS atomic operation to set the new value. Here, using while loops takes into account the situation where multiple threads call at the same time, and then spin-retry is required after CAS fails.
9.long getAndAddLong(Object obj, long offset, long addValue) Method: Get the value of the semantics of the variable volatile with offset in object obj, and set the variable value to the original value + addValue. The usage method is as follows:
public final long getAndAddLong(Object obj, long offset, long addValue) { long l; do { l = getLongVolatile(obj, offset); } while (!compareAndSwapLong(obj, offset, l, l + addValue)); return l; }Similar to the implementation of getAndSetLong, except that when using CAS here, the original value + the value of the transferred incremental parameter addValue is used.
So how to use the Unsafe class?
Seeing that Unsafe is so awesome, do you really want to practice? Okay, let’s first look at the following code:
package com.hjc;import sun.misc.Unsafe;/** * Created by cong on 2018/6/6. */public class TestUnSafe { //Get the instance of Unsafe (2.2.1) static final Unsafe unsafe = Unsafe.getUnsafe(); //Record the offset value of the variable state in the class TestUnSafe (2.2.2) static final long stateOffset; //Variable (2.2.3) private volatile long state = 0; static { try { //Get the offset value of the state variable in the class TestUnSafe (2.2.4) stateOffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state")); } catch (Exception ex) { System.out.println(ex.getLocalizedMessage()); throw new Error(ex); } } public static void main(String[] args) { //Create an instance and set the state value to 1(2.2.5) TestUnSafe test = new TestUnSafe(); //(2.2.6) Boolean sucess = unsafe.compareAndSwapInt(test, stateOffset, 0, 1); System.out.println(sucess); }}Code (2.2.1) gets an instance of Unsafe, and code (2.2.3) creates a variable state initialized to 0.
Code (2.2.4) uses unsafe.objectFieldOffset to obtain the memory offset address of the state variable in the TestUnSafe class in the TestUnSafe object and save it to the stateOffset variable.
Code (2.2.6) calls the compareAndSwapInt method of the created unsafe instance and sets the value of the state variable of the test object. Specifically, if the state variable whose memory offset of the test object is stateOffset is 0, the update value is changed to 1.
We want to enter true in the above code, but after execution, the following results will be output:
Why is this happening? You must enter the getUnsafe code, such as see what is done in it:
private static final Unsafe theUnsafe = new Unsafe(); public static Unsafe getUnsafe(){ // (2.2.7) Class localClass = Reflection.getCallerClass(); // (2.2.8) if (!VM.isSystemDomainLoader(localClass.getClassLoader())) { throw new SecurityException("Unsafe"); } return theUnsafe;} //Judge whether paramClassLoader is a BootStrap class loader (2.2.9) public static boolean isSystemDomainLoader(ClassLoader paramClassLoader){ return paramClassLoader == null; }Code (2.2.7) gets the Class object of the object that calls getUnsafe, here is TestUnSafe.cals.
Code (2.2.8) determines whether it is the localClass loaded by the Bootstrap class loader. The key here is whether the Bootstrap loader loads TestUnSafe.class. Those who have seen the Java virtual machine's class loading mechanism clearly see that it is because TestUnSafe.class is loaded using AppClassLoader, so an exception is directly thrown here.
So the question is, why do you need to make this judgment?
We know that the Unsafe class is provided in rt.jar, and the class in rt.jar is loaded using the Bootstrap class loader. The class where we start the main function is loaded using AppClassLoader, so when loading the Unsafe class in the main function, considering that the parent delegation mechanism will delegate to Bootstrap to load the Unsafe class.
If there is no authentication of code (2.2.8), then our application can use Unsafe to do things at will. The Unsafe class can operate memory directly, which is very unsafe. Therefore, the JDK development team has specially made this restriction, not allowing developers to use Unsafe class under regular channels, but use Unsafe functions in the core class in rt.jar.
The question is, what should we do if we really want to instantiate the Unsafe class and use the Unsafe function?
We should not forget the black technology of reflection, and use universal reflection to obtain Unsafe's instance method. The code is as follows:
package com.hjc;import sun.misc.Unsafe;import java.lang.reflect.Field;/** * Created by cong on 2018/6/6. */public class TestUnSafe { static final Unsafe unsafe; static final long stateOffset; private volatile long state = 0; static { try { // Reflect to get theUnsafe member variable theUnsafe (2.2.10) Field field = Unsafe.class.getDeclaredField("theUnsafe"); // Set to accessable (2.2.11) field.setAccessible(true); // Get the value of this variable (2.2.12) unsafe = (Unsafe) field.get(null); // Get the offset of state in TestUnSafe (2.2.13) stateOffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state")); } catch (Exception ex) { System.out.println(ex.getLocalizedMessage()); throw new Error(ex); } } public static void main(String[] args) { TestUnSafe test = new TestUnSafe(); Boolean success = unsafe.compareAndSwapInt(test, stateOffset, 0, 1); System.out.println(sucess); }}If the above code (2.2.10 2.2.11 2.2.12) reflects the example of unsafe, the running result is as follows:
2. Research on the source code of LockSupport class
LockSupport in rt.jar in JDK is a tool class, and its main function is to suspend and wake up threads. It is the basis for creating locks and other synchronization classes.
The LockSupport class will be associated with each thread that uses it. The thread that calls the method of the LockSupport class by default does not hold a license. LockSupport is implemented internally using the Unsafe class.
Here we should pay attention to several important functions of LockSupport, as follows:
1.void park() method: If the thread calling park() has obtained the license associated with LockSupport, then calling LockSupport.park() will return immediately. Otherwise, the calling thread will be prohibited from participating in the thread's scheduling, that is, it will be blocked and suspended. The following code is the following example:
package com.hjc;import java.util.concurrent.locks.LockSupport;/** * Created by cong on 2018/6/6. */public class LockSupportTest { public static void main( String[] args ) { System.out.println( "park start!" ); LockSupport.park(); System.out.println( "park stop!" ); }}As shown in the above code, directly call the park method in the main function, and the final result will only output the park start! Then the current thread will be suspended, because the calling thread does not hold a license by default. The operation results are as follows:
When you see other threads call the unpark(Thread thread) method and the current thread is used as a parameter, the thread that calls the park method will return. In addition, other threads call the interrupt() method of the blocking thread. When the interrupt flag is set or the blocking thread will return after the thread's false wake-up, it is best to use loop conditions to judge.
It should be noted that the thread that calls the park() method blocked is interrupted by other threads and the blocked thread returns, will not throw an InterruptedException exception.
2.void unpark(Thread thread) method When a thread calls unpark, if the parameter thread thread does not hold the license associated with thread and the LockSupport class, let the thread hold it. If the thread called park() is suspended before and the thread is called, the thread will be awakened after unpark is called.
If the thread has not called park before, after calling the unPark method, the park() method will be returned immediately. The above code is modified as follows:
package com.hjc;import java.util.concurrent.locks.LockSupport;/** * Created by cong on 2018/6/6. */public class LockSupportTest { public static void main( String[] args ) { System.out.println( "park start!" ); //Make the current thread obtain the license LockSupport.unpark(Thread.currentThread()); //Call park LockSupport.park() again; System.out.println( "park stop!" ); }}The operation results are as follows:
Next, we are looking at an example to deepen our understanding of park,unpark, the code is as follows:
import java.util.concurrent.locks.LockSupport;/** * Created by cong on 2018/6/6. */public class LockSupportTest { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("child thread park start!"); // Call the park method and hang yourself LockSupport.park(); System.out.println("child thread unpark!"); } }); //Start the child thread thread.start(); //The main thread sleeps 1S Thread.sleep(1000); System.out.println("main thread unpark start!"); //Call unpark to let the thread hold the license, and then the park method will return LockSupport.unpark(thread); }}The operation results are as follows:
The above code first creates a child thread thread. After startup, the child thread calls the park method. Since the default child thread does not hold a license, it will hang itself.
The main thread sleeps for 1s. The purpose is that the main thread calls the unpark method and lets the child thread output the child thread park start! and block.
The main thread then executes the unpark method, with the parameter being the child thread, the purpose is to let the child thread hold the license, and then the park method called by the child thread returns.
When returning the park method, it will not tell you what reason it is returning. Therefore, the caller needs to check again whether the condition is satisfied based on what park method he was in the current call. If it is not met, he needs to call the park method again.
For example, the interrupt state of a thread when it returns, it can be determined whether it returns because it is interrupted based on the interrupt state comparison before and after the call.
To illustrate that the thread after calling the park method will return after it is interrupted, modify the above example code and delete LockSupport.unpark(thread); and then add thread.interrupt(); the code is as follows:
import java.util.concurrent.locks.LockSupport;/** * Created by cong on 2018/6/6. */public class LockSupportTest { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("Subthread park start!"); // Call the park method and hang yourself. Only interrupts will exit the loop while (!Thread.currentThread().isInterrupted()) { LockSupport.park(); } System.out.println("child thread unpark!"); } }); //Start child thread thread.start(); //Main thread sleeps 1S Thread.sleep(1000); System.out.println("main thread unpark start!"); //Interrupt child thread thread.interrupt(); }}The operation results are as follows:
As the above code, the child thread will only end after the child thread is interrupted. If the child thread is not interrupted, even if you call unPark(Thread) the child thread will not end.
3.void parkNanos(long nanos) method: Similar to park, if the thread calling park has obtained the license associated with LockSupport, then calling LockSupport.park() will return immediately. The difference is that if the thread calling the thread is not obtained, it will be suspended and then returns after nanos time.
park also supports three methods with blocker parameters. When a thread calls park without holding a license and is blocked and suspended, the blocker object will be recorded inside the thread.
Use diagnostic tools to observe the reason why the thread is blocked. The diagnostic tools use the getBlocker (Thread) method to obtain the blocker object. Therefore, JDK recommends that we use the park method with blocker parameters and set the blocker to this, so when the memory dump troubleshoots the problem, we can know which class is blocked.
Examples are as follows:
import java.util.concurrent.locks.LockSupport;/** * Created by cong on 2018/6/6. */public class TestPark { public void testPark(){ LockSupport.park();//(1) } public static void main(String[] args) { TestPark testPark = new TestPark(); testPark.testPark(); }}The operation results are as follows:
You can see that it is running blocking, so we need to use the tools in the JDK/bin directory to take a look. If you don’t know, it is recommended to take a look at the JVM monitoring tools first.
When using jstack pid to view the thread stack after running, the results you can see are as follows:
Then we modify the above code (1) as follows:
LockSupport.park(this);//(1)
Run it again and use jstack pid to view the results as follows:
It can be seen that after the park method with blocker, the thread stack can provide more information about blocking objects.
Then we will check the source code of the park(Object blocker) function, the source code is as follows:
public static void park(Object blocker) { //Get the calling thread Thread t = Thread.currentThread(); //Set the blocker variable setBlocker(t, blocker); //Hang the thread UNSAFE.park(false, 0L); //Clear the blocker variable after the thread is activated, because the reason is usually analyzed when the thread is blocked setBlocker(t, null);}There is a variable in the Thread class volatile Object parkBlocker. It is used to store the blocker object passed by the park, that is, the blocker variable is stored in the member variable of the thread calling the park method.
4. The void parkNanos(Object blocker, long nanos) function has an additional timeout time compared to park(Object blocker).
5.void parkUntil(Object blocker, long deadline) The source code of parkUntil is as follows:
public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); //isAbsolute=true,time=deadline; means that UNSAFE.park(true, deadline); setBlocker(t, null); }You can see that it is a set deadline, the time unit is milliseconds, which is converted into a value after milliseconds from 1970 to the present time point. The difference between this and parkNanos (Object blocker, long nanos) is that the latter calculates the waiting nanos time from the current time, while the former specifies a time point.
For example, we need to wait until 20:34 on 2018.06.06, and then convert this time point into the total number of milliseconds from 1970 to this time point.
Let's look at another example, the code is as follows:
import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;import java.util.concurrent.atomic.AtomicBoolean;import java.util.concurrent.locks.LockSupport;/** * Created by cong on 2018/6/6. */public class FIFOMutex { private final AtomicBoolean locked = new AtomicBoolean(false); private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>(); public void lock() { boolean wasInterrupted = false; Thread current = Thread.currentThread(); waiters.add(current); // Only the thread of the head of the team can obtain the lock (1) while (waiters.peek() != current || !locked.compareAndSet(false, true)) { LockSupport.park(this); if (Thread.interrupted()) // (2) wasInterrupted = true; } waiters.remove(); if (wasInterrupted) // (3) current.interrupt(); } public void unlock() { locked.set(false); LockSupport.unpark(waiters.peek()); } }You can see that this is a first-in-first-out lock, that is, only the queue header element can obtain it. Code (1) If the current thread is not the queue header or the current lock has been acquired by other threads, then call the park method to suspend itself.
Then the code (2) makes a judgment. If the park method returns because it is interrupted, the interrupt is ignored, and the interrupt flag is reset, and only a flag is made, and then again determine whether the current thread is the head element of the queue or whether the lock has been acquired by other threads. If so, continue to call the park method to hang yourself.
Then if the mark is true in code (3), the thread will be interrupted. How do you understand this? In fact, other threads interrupted the thread. Although I am not interested in the interrupt signal and ignore it, it does not mean that other threads are not interested in the flag, so I need to restore it.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.