Shooting yourself in the foot with Random number generators
This is not going to be one of the posts explaining how a random number generator is not so random after all. So those of you expecting a guideline for how to hack a slot machine, move along, nothing to see here.
Instead, it is a post about one of the not-so-uncommon lock contention issues, hidden inside random number generators in Java APIs.
To open up the subject, lets start by looking into how the concurrency is handled in java.util.Random class. The instances of java.util.Random are thread-safe. However, the concurrent use of the same java.util.Random instance across threads is synchronized and as we have found out tends to trigger contention issues affecting performance of the application.
In your regular day-to-day enterprise app it might not sound as an important issue – after all, how often do you actually do something that is deliberately unpredictable? Instead, you are all about predictably following business rules. I have to admit though that in some cases these business rules tend to involve even more entropy than a truly random seed generation algorithm would, but this would be a different story altogether.
But the devil is hidden in the details, which in this case happens to be a subclass of the java.util.Random, namely java.util.SecureRandom. This class, as the name states should be used in cases where the outcome of the random number generator has to be cryptographically secure. For reasons unknown to mankind, this implementation has been chosen to be the backbone in many common APIs in situations where one normally would not expect the cryptographically secure aspects of the randomness to be of significance.
We have experienced the problem firsthand by keeping a close eye on the adoption of our lock contention detection solution. Based on the results, one of the most common locking issues within Java applications is triggered through an innocent-looking java.io.File.createTempFile() calls. Under the hood, this temporary file creation is relying upon a SecureRandom class to calculate the name of the file.
private static final SecureRandom random = new SecureRandom(); static File generateFile(String prefix, String suffix, File dir) { long n = random.nextLong(); if (n == Long.MIN_VALUE) { n = 0; // corner case } else { n = Math.abs(n); } return new File(dir, prefix + Long.toString(n) + suffix); }
And SecureRandom, when nextLong is called, eventually calls its method nextBytes(), which is defined as synchronized:
synchronized public void nextBytes(byte[] bytes) { secureRandomSpi.engineNextBytes(bytes); }
One may say, that if I create new SecureRandom in each thread, I will not get any issues. Unfortunately, it’s not that simple. SecureRandom uses an implementation of java.security.SecureRandomSpi, which will eventually be contended anyhow (you may look at the following bug discussion with some benchmarks in Jenkins issue tracker)
This in combination with certain application usage patterns (especially if you have lots of SSL connections which rely on SecureRandom for their crypto-handshaking magic) has a tendency to build up into long-lasting contention issues.
The fix to the situation is simple if you can control the source code – just rebuild the solution to rely upon the java.util.ThreadLocalRandom for multithreaded designs. In cases where you are stuck with a standard API making the decisions for you the solution can be more complex and require significant refactoring.
Moral of the story? Concurrency is hard. Especially when the building blocks of your system have not taken this into account. In any case, I do hope the article is saving the world at least from couple of new libraries being born where the random number generators will become a contention point.
Reference: | Shooting yourself in the foot with Random number generators from our JCG partner Vladimir Sor at the Plumbr Blog blog. |