preface

When I was writing some business code recently, I encountered a scenario that needed to generate Random numbers, and I naturally thought of the Random class in the JDK package.

However, in pursuit of the extreme performance, I considered using the ThreadLocalRandom class for optimization. In the process of checking the Implementation of ThreadLocalRandom, I searched for part of the code, learned a lot from the Unsafe process, and solved many doubts through searching and asking questions. Then summarize the cost text.

Random performance issues

When using the Random class, in order to avoid the overhead of repeated creation, we usually set the instantiated Random object to the property or static property of the service object we use. This is fine when threads are not competitive, but in a highly concurrent Web service, Using the same Random object can cause a thread to block.

The Random principle of Random is to carry out fixed arithmetic and bit operations on a “Random seed” to obtain a Random result, and then use this result as the next Random seed. When addressing thread-safety issues, Random uses CAS to update the next Random seed. It can be assumed that if multiple threads use this object at the same time, some threads must fail to execute CAS continuously, resulting in thread blocking.

ThreadLocalRandom

The JDK developers naturally took this into account and added the ThreadLocalRandom class to the Concurrent package. When I first saw the name of the class, I thought it was implemented through ThreadLocal, and then I thought of the horrible memory leak. Clicking on the source code, however, shows no sign of ThreadLocal, as there is plenty of unsafe-related code.

Let’s look at its core code:

UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA);
Copy the code

A more intuitive Java code translation would look like this:

Thread t = Thread.currentThread();
long r = UNSAFE.getLong(t, SEED) + GAMMA;
UNSAFE.putLong(t, SEED, r);
Copy the code

Get the key of the current object as thread.currentThread () and the SEED as value.

However, using objects as keys can cause memory leaks. Since Thread objects can be created in large numbers, removing the values in the Map will cause the Map to grow larger and overflow memory.

Unsafe

function

A closer look at the core code of the ThreadLocalRandom class shows that it is not a simple Map operation. Its getLong() method requires two parameters, while putLong() method requires three parameters. We can’t see the implementation. The two method signatures are:

public native long getLong(Object var1, long var2);
public native void putLong(Object var1, long var2, long var4);
Copy the code

Although we can’t see the concrete implementation, we can find the function of the two methods. Here is the function description of the two methods:

  • PutLong (object, offset, value) Can set the last four bytes of the memory address of an object offset to value.
  • GetLong (object, offset) reads four bytes from the object’s memory address offset and returns them as long.

No security

As a method in the Unsafe class, it also has the “Unsafe” aspect of being able to manipulate memory directly without any security checks, and if there is a Fatal Error thrown at runtime, the entire virtual machine will exit.

Unsafe.getlong (), however, is a very safe method that reads four bytes from an unsafe-looking memory location. Whatever those four bytes are, they are always converted to longs. Whether the long result matches the business is another matter. The set method is also safer, overwriting the four bytes following a memory location into a long value with almost no error.

So what is “unsafe” about these two methods?

Their insecurity is not that errors are reported during the execution of the two methods, but that unprotected changes to memory cause other methods to report errors when using that memory.

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    // Unsafe sets the constructor private, while getUnsafe gets the instance method package private, which can only be obtained by reflection outside the package
    Field field = Unsafe.class.getDeclaredField("theUnsafe"); 
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(null);
    // The Test class is a writable Test class, with only one String Test class
    Test test = new Test();
    test.ttt = "12345";
    unsafe.putLong(test, 12L, 2333L);
    System.out.println(test.value);
}
Copy the code

Running the above code results in A fatal error saying “A fatal error has been detected by the Java Runtime Environment:… Process finished with exit code 134 (Interrupted by Signal 6: SIGABRT) “.

Unsafe, I used the value property of the Test class to set the position of the long value 2333, and when I used the value property, The virtual machine will parse this chunk of memory into a String object, the structure of the original String object header is upset, parsing the object fails to throw an error, the more serious problem is that the error message does not contain the class name, line number and other information, troubleshooting this problem in a complex project is like looking for a needle in a haystack.

Unsafe’s other methods aren’t necessarily like this pair, though, and using them may require additional security concerns that need to be addressed later.

The realization of the ThreadLocalRandom

If ThreadLocalRandom is safe, let’s go back and look at its implementation.

The implementation of ThreadLocalRandom requires the coordination of the Thread object. There is an attribute threadLocalRandomSeed in the Thread object, which stores the random seed specific to the Thread. This attribute is offset in the Thread object. Is determined when the ThreadLocalRandom class is loaded, The specific method is to SEED = UNSAFE. ObjectFieldOffset (Thread. Class. GetDeclaredField (” threadLocalRandomSeed “));

Unsafe.objectFieldOffset(class, fieldName) is used to determine the size of an object’s memory after its class is loaded. Unsafe.objectFieldOffset(class, fieldName) is used to determine the offset of an attribute in a class. Using ThreadLocalRandom is safe.

doubt

In the process of finding these questions, I also had two questions.

Usage scenarios

Addressing broadening concerns over food, why would ThreadLocalRandom even need to modify random seeds in threadobjects? Wouldn’t it be easier to add get/set methods to the Unsafe Thread object?

Someone on stackOverFlow had the same question as me:

Stackoverflow.com/questions/4…

The accepted answer explains that, for JDK developers, both the insecure and get/set methods are like ordinary tools, and there is no rule over which to use.

ThreadLocalRandom and Thread are not in the same package. If you add a get/set method, the get/set method must be set to public. This violates the principle of class closure.

Memory layout

Another question is that after looking over Unsafe. ObjectFieldOffset, I can obtain the offset of the property in the object’s memory. I tried the Test class mentioned above using the main method in IDEA. The only property of the Test class, value, has an offset of 12 bytes relative to the memory of the object.

We know that the object header of a Java object is at the beginning of the memory of a Java object, and the MarkWord of an object is at the beginning of the object header, and on a 32-bit system it takes four bytes, on a 64-bit system it takes eight bytes, and I’m using a 64-bit system, This will no doubt take up an 8-byte offset.

MarkWord contains the pointer to the Test class and the length of the array object. The array is 4 bytes long, but the Test class is not an array and has no other properties.

The only possibility is that the virtual machine has pointer compression enabled. Pointer compression can only be enabled on 64-bit systems, and the pointer type takes up only 4 bytes, but I don’t show that pointer compression is specified. Pointer compression is enabled by default after 1.8. When -xx: -usecompressedoops is enabled, the offset of value is 16.

summary

When writing code, pay attention to the implementation of the dependency library. Otherwise, you may stumble into some unexpected pitfalls, and it doesn’t hurt to look around.

From: zhenbianshu. Making. IO