1, Overview
LockSupport class, a tool class in the JUC package, is used to create basic thread blocking primitives for locks and other synchronization classes.
Basic thread blocking primitives for creating locks and other synchronization classes
The core methods of the LockSupport class are actually two: park() and unpark(), where the park() method is used to block the current calling thread, and the unpark() method is used to wake the specified thread.
In fact, this is somewhat similar to the wait() and signal() methods of the Object class, but these two methods of the LockSupport class are semantically clearer than those of the Object class, and can block and wake up the specified thread.
LockSupport class uses a concept called permission to block and wake up threads. You can regard permission as a (0,1) Semaphore, but unlike Semaphore, the upper limit of permission accumulation is 1.
Initially, the permission is 0. When the unpark() method is called, the permission of the thread is increased by 1. When the park() method is called, if the permission is 0, the calling thread enters the blocking state.
2, LockSupport thread control
1. Usage
LockSupport has two core methods:
/* park */ public static void park() { U.park(false, 0L); } /* unpark */ public static void unpark(Thread thread) { if (thread != null) U.unpark(thread); }
For example, test how to block threads:
import java.util.concurrent.locks.LockSupport; public class UseLockSupport { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("thread " + Thread.currentThread().getName() + " Start execution and park wait for"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.park(); System.out.println("thread " + Thread.currentThread().getName() + " relieve park Wait for execution to continue"); } }); t.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.unpark(t); } }
Operation results:
2. Underlying implementation
LockSupport controls threads based on the thread scheduling function of Unsafe class:
/* park */ public static void park() { U.park(false, 0L); } /* unpark */ public static void unpark(Thread thread) { if (thread != null) U.unpark(thread); }
3, Unsafe core class
Unsafe class enables Java to operate memory space like C language pointers:
- It is not managed by the JVM, which means it cannot be GC. We need to manually GC, and memory leakage will occur if we are careless.
- In Unsafe methods, the original address (memory address) and the address of the replaced object must be provided. The offset needs to be calculated by the programmer. If there is a problem, the entire JVM instance will crash and the application will crash
- Direct operation of memory, which is faster and can improve efficiency under the condition of high concurrency.
1. Create object
Unsafe class is final and inheritance is not allowed.
In the JDK, to create a sun.misc.Unsafe object, you need to call the getUnsafe() method, which adopts the singleton mode to ensure that there is only one object in the world:
/* sun.misc.Unsafe Class partial code */ //Private construction method private Unsafe() {} //Static variable (final singleton) private static final Unsafe theUnsafe = new Unsafe(); //Gets the singleton factory of the object @CallerSensitive public static Unsafe getUnsafe() { //Gets the ClassLoader loader for the current class Class<?> caller = Reflection.getCallerClass(); //Is the caller a BootStrapLoader if (!VM.isSystemDomainLoader(caller.getClassLoader())) //No: an exception is thrown throw new SecurityException("Unsafe"); //Yes: return object return theUnsafe; }
Condition for obtaining Unsafe class object: the current class loader is BootStrapLoader, otherwise an exception will be thrown:
In order to obtain the sun.misc.Unsafe object, we can use reflection to directly obtain the internally defined singleton:
/* Get method: (possible exceptions: IllegalAccessException, NoSuchFieldException) */ //Get singleton Field object Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Set private access to true f.setAccessible(true); //Get object Unsafe unsafe = (Unsafe) f.get(null);
sun.misc.Unsafe internally depends on jdk.internal.misc.Unsafe, but we cannot directly obtain jdk.internal.misc.Unsafe objects (including reflection, because compilation fails).
2. Built in method
Our presentation of methods comes from the jdk.internal.misc.Unsafe class
① Ordinary reading and writing
Any object can be read and written through the Unsafe built-in method, whether it is private or not:
/* Annotation @ HotSpotIntrinsicCandidate: There is an efficient implementation in HotSpot, which is based on CPU instructions. The efficient implementation of runtime HotSpot maintenance will replace the source implementation of JDK, so as to obtain higher efficiency. */ @HotSpotIntrinsicCandidate public native int getInt(Object o, long offset); //Read method, o represents the object that needs to manipulate the internal properties, and offset represents the offset @HotSpotIntrinsicCandidate public native void putInt(Object o, long offset, int x); //Write method, x represents the value you want to write
In addition, you can read and write directly on the address:
//Get the local pointer from a given memory address. If it is not the result of the allocateMemory method, the result will be uncertain @ForceInline public long getAddress(Object o, long offset) { if (ADDRESS_SIZE == 4) { return Integer.toUnsignedLong(getInt(o, offset)); } else { return getLong(o, offset); } } //Store a local pointer to a given memory address. If it is not the allocateMemory method, the result will be uncertain @ForceInline public void putAddress(Object o, long offset, long x) { if (ADDRESS_SIZE == 4) { putInt(o, offset, (int)x); } else { putLong(o, offset, x); } }
In addition to basic data types, it also provides reading and writing methods for reference types:
// The following deprecated methods are used by JSR 166. @Deprecated(since="12", forRemoval=true) public final Object getObject(Object o, long offset) { return getReference(o, offset); } @Deprecated(since="12", forRemoval=true) public final void putObject(Object o, long offset, Object x) { putReference(o, offset, x); }
Unfortunately, after JDK12, these reference type reading and writing methods are "outdated" and are not recommended. Instead, these methods are:
@HotSpotIntrinsicCandidate public native Object getReference(Object o, long offset); @HotSpotIntrinsicCandidate public native void putReference(Object o, long offset, Object x);
② volatile read and write
Ordinary reading and writing cannot guarantee visibility and order, while volatile reading and writing method avoids this problem:
Basic data type:
@HotSpotIntrinsicCandidate public native void putIntVolatile(Object o, long offset, int x); @HotSpotIntrinsicCandidate public native int getIntVolatile(Object o, long offset);
Reference type:
@HotSpotIntrinsicCandidate public native Object getReferenceVolatile(Object o, long offset); @HotSpotIntrinsicCandidate public native void putReferenceVolatile(Object o, long offset, Object x);
③ Sequential write
Orderly writing only ensures the order of writing, not the visibility, that is, the writing of one thread does not guarantee the immediate visibility of other threads.
/* sun.misc.Unsafe */ @ForceInline public void putOrderedInt(Object o, long offset, int x) { theInternalUnsafe.putIntRelease(o, offset, x); }
This method relies on the method to complete orderly writing:
/* jdk.internal.misc.Unsafe */ @HotSpotIntrinsicCandidate public final void putIntRelease(Object o, long offset, int x) { putIntVolatile(o, offset, x); }
In addition, there are methods corresponding to other types and reference types, which are not listed here.
④ CAS operation
CAS operation is widely used in JUC, and CAS operation is the basis of JUC. Several types of CAS operations are available in Unsafe:
/* int CAS operation */ @HotSpotIntrinsicCandidate public final native boolean compareAndSetInt(Object o, long offset, int expected, int x); } /* Object CAS operation */ @HotSpotIntrinsicCandidate public final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x);
⑤ Direct memory operation
Unsafe class provides us with the ability to directly operate memory, and these methods depend on some native methods. In order to distinguish, the local methods end with the number 0.
Allocate memory
/* Allocate memory */ public long allocateMemory(long bytes) { bytes = alignToHeapWordSize(bytes); //Allocate memory check allocateMemoryChecks(bytes); if (bytes == 0) { return 0; } //Allocate memory for it long p = allocateMemory0(bytes); //Unable to allocate memory, exception thrown if (p == 0) { throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes"); } //Return allocation results return p; }
Reallocate memory
/* Reallocate memory */ public long reallocateMemory(long address, long bytes) { bytes = alignToHeapWordSize(bytes); //Reallocate memory check reallocateMemoryChecks(address, bytes); if (bytes == 0) { freeMemory(address); return 0; } //Check: allocate new memory OR reallocate memory long p = (address == 0) ? allocateMemory0(bytes) : reallocateMemory0(address, bytes); //Unable to allocate memory, exception thrown if (p == 0) { throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes"); } //Return allocation results return p; }
Memory initialization
/* Memory initialization */ public void setMemory(Object o, long offset, long bytes, byte value) { setMemoryChecks(o, offset, bytes, value); //Memory initialization check if (bytes == 0) { return; } setMemory0(o, offset, bytes, value); //Memory initialization }
Memory copy
/* Memory copy */ public void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes) { copyMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes); //Memory copy check if (bytes == 0) { return; } copyMemory0(srcBase, srcOffset, destBase, destOffset, bytes); //Memory copy }
Clear memory
/* Clear memory */ public void freeMemory(long address) { freeMemoryChecks(address); //Clear memory check if (address == 0) { //Check address return; } freeMemory0(address); //Clear memory }
⑥ Thread scheduling
@HotSpotIntrinsicCandidate public native void park(boolean isAbsolute, long time); @HotSpotIntrinsicCandidate public native void unpark(Object thread);
⑦ Memory barrier
- loadFence: ensure that all read operations before this barrier have been completed.
- storeFence: ensure that all write operations before this barrier have been completed.
- fullFence: ensure that all read and write operations before this barrier have been completed.
@HotSpotIntrinsicCandidate public native void loadFence(); @HotSpotIntrinsicCandidate public native void storeFence(); @HotSpotIntrinsicCandidate public native void fullFence();
3. Special applications
① Ignore construction method
public static void main(String[] args) throws Exception { Unsafe unsafe = null; try{ Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe) f.get(null); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } A o1 = new A(); // Construction method o1.a(); // prints 1 A o2 = A.class.getDeclaredConstructor().newInstance(); //reflex o2.a(); // prints 1 A o3 = (A) unsafe.allocateInstance(A.class); // Unsafe.allocateInstance() o3.a(); // prints 0 } static class A { private long a; // It is not initialized by the constructor. The default value is 0 public A() { this.a = 1; // initialization } public long a() { System.out.println(this.a); return this.a; } }
Operation results:
② Giant array
//Define unsafe objects private static Unsafe unsafe; public static void main(String[] args) throws Exception { /* Get object */ Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe) f.get(null); // Set the array size to integer.max_ 2 times value long SUPER_SIZE = (long) Integer.MAX_VALUE * 2; SuperArray array = new SuperArray(SUPER_SIZE); System.out.println("Array size:" + array.size()); // 4294967294 int sum = 0; for (int i = 0; i < 100; i++) { array.set((long) Integer.MAX_VALUE + i, (byte) 3); sum += array.get((long) Integer.MAX_VALUE + i); } System.out.println("Sum of 100 elements:" + sum); // The total should be 300 } private static Unsafe getUnsafe() { return unsafe; } static class SuperArray { private final static int BYTE = 1; private long size; private long address; public SuperArray(long size) { this.size = size; address = getUnsafe().allocateMemory(size * BYTE); } public void set(long i, byte value) { getUnsafe().putByte(address + i * BYTE, value); } public int get(long idx) { return getUnsafe().getByte(address + idx * BYTE); } public long size() { return size; } }
Operation results: