Monday, 17 October 2016

8 Hidden Great Java 8 Features

If you haven’t seen some of the videos or tutorials around Java 8, you’ve probably been super-busy or have a more interesting social life than I do (which isn’t saying much). With new features like lambda expressions and Project Nashorn taking so much of the spotlight, I wanted to focus on some new APIs that have been a bit under the radar, but make Java 8 better in so many ways.

1. Stamped Locks


Multi-threaded code has long been the bane of server developers (just ask Oracle Java Language Architect and concurrency guru Brian Goetz). Over time complex idioms were added to the core Java libraries to help minimize thread waits when accessing shared resources.

A capability-based lock with three modes for controlling read/write access. The state of a StampedLock consists of a version and mode. Lock acquisition methods return a stamp that represents and controls access with respect to a lock state; "try" versions of these methods may instead return the special value zero to represent failure to acquire access. Lock release and conversion methods require stamps as arguments, and fail if they do not match the state of the lock. The three modes are:
  • Writing. Method writeLock() possibly blocks waiting for exclusive access, returning a stamp that can be used in method unlockWrite(long) to release the lock. Untimed and timed versions of tryWriteLock are also provided. When the lock is held in write mode, no read locks may be obtained, and all optimistic read validations will fail.
  • Reading. Method readLock() possibly blocks waiting for non-exclusive access, returning a stamp that can be used in method unlockRead(long) to release the lock. Untimed and timed versions of tryReadLock are also provided.
  • Optimistic Reading. Method tryOptimisticRead() returns a non-zero stamp only if the lock is not currently held in write mode. Method validate(long) returns true if the lock has not been acquired in write mode since obtaining a given stamp. This mode can be thought of as an extremely weak version of a read-lock, that can be broken by a writer at any time. The use of optimistic mode for short read-only code segments often reduces contention and improves throughput. However, its use is inherently fragile. Optimistic read sections should only read fields and hold them in local variables for later use after validation. Fields read while in optimistic mode may be wildly inconsistent, so usage applies only when you are familiar enough with data representations to check consistency and/or repeatedly invoke method validate(). For example, such steps are typically required when first reading an object or array reference, and then accessing one of its fields, elements or methods.

StampedLock has an "optimistic" mode that issues a stamp that is returned by each locking operation to serve as a sort of admission ticket; each unlock operation needs to be passed its correlating stamp. Any thread that happens to acquire a write lock while a reader was holding an optimistic lock, will cause the optimistic unlock to be invalidated (the stamp is no longer valid). At that point the application can start all over, perhaps with a pessimistic lock (also implemented in StampedLock.) Managing that is up to you, and one stamp cannot be used to unlock another – so be super careful.On paper this sounds great. The problem is that the ReadWriteLock can be super slow(up to 10x), which kind of defeats its purpose. Java 8 introduces a new ReadWrite lock – called StampedLock. The good news here is that this guy is seriously fast. The bad news is that it’s more complicated to use and lugs around more state. It’s also not reentrant, which means a thread can have the dubious pleasure of deadlocking against itself.
Let’s see this lock in action-
 class Point {
   private double x, y;
   private final StampedLock sl = new StampedLock();

   void move(double deltaX, double deltaY) { 
// an exclusively locked method
     long stamp = sl.writeLock();
     try {
       x += deltaX;
       y += deltaY;
     } finally {
       sl.unlockWrite(stamp);
     }
   }

   double distanceFromOrigin() { // A read-only method
     long stamp = sl.tryOptimisticRead();
     double currentX = x, currentY = y;
     if (!sl.validate(stamp)) {
        stamp = sl.readLock();
        try {
          currentX = x;
          currentY = y;
        } finally {
           sl.unlockRead(stamp);
        }
     }
     return Math.sqrt(currentX * currentX + currentY * currentY);
   }

   void moveIfAtOrigin(double newX, double newY) { // upgrade
     // Could instead start with optimistic, not read mode
     long stamp = sl.readLock();
     try {
       while (x == 0.0 && y == 0.0) {
         long ws = sl.tryConvertToWriteLock(stamp);
         if (ws != 0L) {
           stamp = ws;
           x = newX;
           y = newY;
           break;
         }
         else {
           sl.unlockRead(stamp);
           stamp = sl.writeLock();
         }
       }
     } finally {
       sl.unlock(stamp);
     }
   }
 }

 

2. Concurrent Adders

Another beautiful addition to Java 8, meant specifically for code running at scale, is the concurrent “Adders”. One of the most basic concurrency patterns is reading and writing the value of a numeric counter. As such, there are many ways in which you can do this today, but none so efficient or elegant as what Java 8 has to offer.
Up until now this was done using Atomics, which used a direct CPU compare and swap (CAS) instruction (via the sun.misc.Unsafe class) to try and set the value of a counter. The problem was that when a CAS failed due to contention, the AtomicInteger would spin, continually retrying the CAS in an infinite loop until it succeeded. At high levels of contention this could prove to be pretty slow.
Enter Java 8’s LongAdders(http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html ). This set of classes provides a convenient way to concurrently read and write numeric values at scale. Usage is super simple. Just instantiate a new LongAdder and use its add() and intValue() methods to increase and sample the counter.
The difference between this and the old Atomics is that here, when a CAS fails due to contention, instead of spinning the CPU, the Adder will store the delta in an internal cell object allocated for that thread. It will then add this value along with any other pending cells to the result of intValue(). This reduces the need to go back and CAS or block other threads.
If you’re asking yourself when should I prefer to use concurrent Adders over Atomics to manage counters? The simple answer is – always.

3. Parallel Computaion on Arrays


1. parallelSort

Just as concurrent Adders speed up counting, Java 8 delivers a concise way to speed up sorting. The recipe is pretty simple. Instead of -
Array.sort(myArray);
You can now use –
Arrays.parallelSort(myArray);
This will automatically break up the target collection into several parts, which will be sorted independently across a number of cores and then grouped back together. The only caveat here is that when called in highly multi-threaded environments, such as a busy web container, the benefits of this approach will begin to diminish (by more than 90%) due to the cost of increased CPU context switches.
2. parallelPrefix

There are cases where we would like to perform operations in each element of an array (or in a range of them) based on the previous value. This is possible by using the method parallelPrefix():
01DoubleBinaryOperator binaryOperator = new DoubleBinaryOperator()
02{
03    @Override
04    public double applyAsDouble( double x, double y )
05    {
06        return x + y;
07    }
08};
09// we can use binary operators as prefix in parallel
10Arrays.parallelPrefix( array, binaryOperator );
11// also using lambdas
12Arrays.parallelPrefix( array, ( x, y ) -> x + y );
In the code above we can see that it is also possible to use Lambda expressions directly as parameter, as it was for the method parallelSetAll().
If we want to perform these operations only in a range of the array elements we can pass the start and end indexes and the operation will only be done between the range extremes:
1int startIndex = 100;
2int endIndex = 150;
3Arrays.parallelPrefix( array, startIndex, endIndex, ( x, y ) -> x + y );
The method parallelPrefix() is not that intuitive and, in my opinion, its name is confusing; so I am going to provide a very basic example that would help to understand a bit this method. If we have an array like the following one:
1double[] array = {1.02.03.0};
And we want to apply to every entry a calculation based in the previous value, for example, if previous value is odd, we multiply the current entry by 2, otherwise leave the value as it is, so more or less we are going to multiply by 2 all entries which previous value is odd (I do not know why we would like to do this, but it is just an example). We can write something like the following (using Lambdas):
1Arrays.parallelPrefix( array, ( x, y ) -> {
2    if( x % 2 != 0 )
3        return y * 2;
4    return y;
5} );
The result of printing out this array before and after the operation is done would be:
1element at pos 0 previously 1.0
2element at pos 1 previously 2.0
3element at pos 2 previously 3.0
4element at pos 0 after 1.0
5element at pos 1 after 4.0
6element at pos 2 after 3.0
We can see how the only entry that has been changed is the second one because its previous value is odd.


4. Switching to the new Date API


http://www.oracle.com/technetwork/articles/java/jf14-date-time-2125367.html
Java 8 introduces a complete new date-time API.  You kind of know it’s about time when most of the methods of the current one are marked as deprecated... The new API brings ease-of-use and accuracy long provided by the popular Joda time API into the core Java library.
As with any new API the good news is that it’s more elegant and functional. Unfortunately there are still vast amounts of code out there using the old API, and that won’t change any time soon.
To help bridge the gap between the old and new API’s, the venerable Date class now has a new method called toInstant() which converts the Date into the new representation. This can be especially effective in those cases where you're working on an API that expects the classic form, but would like to enjoy everything the new API has to offer.


LocalDate and LocalTime
The first classes you will probably encounter when using the new API are LocalDate and LocalTime. They are local in the sense that they represent date and time from the context of the observer, such as a calendar on a desk or a clock on your wall. There is also a composite class called LocalDateTime, which is a pairing of LocalDate and LocalTime. 

All the core classes in the new API are constructed by fluent factory methods. When constructing a value by its constituent fields, the factory is called of; when converting from another type, the factory is called from. 



LocalDateTime timePoint = LocalDateTime.now();     // The current date and time
LocalDate.of(2012, Month.DECEMBER, 12); // from values
LocalDate.ofEpochDay(150);  // middle of 1970
LocalTime.of(17, 18); // the train I took home today
LocalTime.parse("10:15:30"); // From a String

5. Controlling OS Processes

Launching an OS process from within your code is right there with JNI calls – it’s something you do half-knowing there’s a good chance you’re going to get some unexpected results and some really bad exceptions down the line.
Even so, it’s a necessary evil. But processes have another nasty angle to them - they have a tendency to dangle. The problem with launching process from within Java code so far has been that is was hard to control a process once it was launched.
To help us with this Java 8 introduces three new methods in the Process class -
  1. destroyForcibly - terminates a process with a much higher degree of success than before.
  2. isAlive tells if a process launched by your code is still alive.
  3. A new overload for waitFor() lets you specify the amount of time you want to wait for the process to finish. This returns whether the process exited successfully or timed-out in which case you might terminate it.
Two good use-cases for these new methods are -
  • If the process did not finish in time, terminate and move forward:
if (process.wait(MY_TIMEOUT, TimeUnit.MILLISECONDS)){
       //success! }
else {
    process.destroyForcibly();
}
  • Make sure that before your code is done, you're not leaving any processes behind. Dangling processes can slowly but surely deplete your OS.
for (Process p : processes) {
       if (p.isAlive()) {
             p.destroyForcibly();
       }
}

6. Exact Numeric Operations

Numeric overflows can cause some of the nastiest bugs due to their implicit nature. This is especially true in systems where int values (such as counters) grow over time. In those cases things that work well in staging, and even during long periods in production, can start breaking in the weirdest of ways, when operations begin to overflow and produce completely unexpected values.
To help with this Java 8 has added several new “exact” methods to the Math class geared towards protecting sensitive code from implicit overflows, by throwing an unchecked ArithmeticException when the value of an operation overflows its precision.
int safeC = Math.multiplyExact(bigA, bigB); // will throw ArithmeticException if result exceeds +-2^31
The only downside is that it’s up to you to find those places in your code where overflows can happen. Not an automagical solution by any stretch, but I guess it’s better than nothing.

7. Secure Random Generation

Java has been under fire for several years for having security holes. Justified or not, a lot of workhas been done to fortify the JVM and frameworks from possible attacks. Random numbers with a low-level of entropy make systems that use random number generators to create encryption keys or hash sensitive information more susceptible to hacking.
So far selection of the Random Number Generation algorithms has been left to the developer. The problem is that where implementations depend on specific hardware / OS / JVM, the desired algorithm may not be available. In such cases applications have a tendency to default to weaker generators, which can put them at greater risk of attack.
Java 8 has added a new method called SecureRandom.getInstanceStrong() whose aim is to have the JVM choose a secure provider for you. If you’re writing code without complete control of the OS / hardware / JVM on which it would run (which is very common when deploying to the cloud or PaaS), my suggestion is to give this approach some serious consideration.

8. Optional References

NulPointers are like stubbing your toes - you’ve been doing it since you could stand up, and no matter how smart you are today - chances are you still do. To help with this age-old problem Java 8 is introducing a new template called Optional<T>.  
Borrowing from Scala and Haskell, this template is meant to explicitly state when a reference passed to or returned by a function can be null. This is meant to reduce the guessing game of whether a reference can be null, through over-reliance on documentation which may be out-of-date, or reading code which may change over time.
Optional<User> tryFindUser(int userID) {
or -
void processUser(User user, Optional<Cart> shoppingCart) {
The Optional template has a set of functions that make sampling it more convenient, such asisPresent() to check if an non-null value is available, or  ifPresent() to which you can pass a Lambda function that will be executed if isPresent is true. The downside is that much like with Java 8’s new date-time APIs, it will take time and work till this pattern takes hold and is absorbed into the libraries we use and design everyday.
New Lambda syntax for printing an optional value -
value.ifPresent(System.out::print);

No comments:

Post a Comment