Useful & Unknown Java Features

Useful & Unknown Java Features

In this article, you will learn about some useful Java features, you probably didn’t hear of. That’s my private list of features I used recently or I just came across while reading articles about Java. I’ll focus not on the language aspects, but rather on the API. I have already published all the examples related to this article on Twitter in a form visible below. You may also find them on my Twitter account or just under the #java hashtag.

java-features-twitter

Do you like Java and want to be up-to-date with the latest features? You can also read my article about new features after Java 8. Below, you can find a list of eight unknown and useful described in the current article. Let’s begin!

1. Delay Queue

As you know, there are many types of collections available in Java. But did you hear about DelayQueue? It is a specific type of Java collection that allows us to sort elements based on their delay time. It is a very interesting class, to be honest. Although the DelayQueue class is a member of the Java Collections it belongs to the java.util.concurrent package. It implements the BlockingQueue interface. The elements can be taken from the queue only if their time has expired.

In order to use it, first, your class needs to implement the getDelay method from the Delayed interface. It does not have to be a class – you can use Java Record as well.

public record DelayedEvent(long startTime, String msg) implements Delayed {

    public long getDelay(TimeUnit unit) {
        long diff = startTime - System.currentTimeMillis();
        return unit.convert(diff, TimeUnit.MILLISECONDS);
    }

    public int compareTo(Delayed o) {
        return (int) (this.startTime - ((DelayedEvent) o).startTime);
    }

}

Let’s say we would like to delay the element 10 seconds. We just need to set the current time increased by 10 seconds on our DelayedEvent class.

final DelayQueue<DelayedEvent> delayQueue = new DelayQueue<>();
final long timeFirst = System.currentTimeMillis() + 10000;
delayQueue.offer(new DelayedEvent(timeFirst, "1"));
log.info("Done");
log.info(delayQueue.take().msg());

What’s the output of the code visible above? Let’s see.

2. Period of Days in Time Format

Ok, maybe it won’t be one of the Java features very useful for most of you ๐Ÿ™‚ But to be honest, I have a soft spot for that feature… Anyway, Java 8 has improved a lot the time processing API. Starting from this version of Java you probably won’t have to use any additional library like Joda Time in most cases. Could you imagine that since Java 16 you may even express periods in a day, such as โ€œin the morningโ€ or โ€œin the afternoonโ€ using a standard formatter? There is a new format pattern for that called B.

String s = DateTimeFormatter
  .ofPattern("B")
  .format(LocalDateTime.now());
System.out.println(s);

Here’s my result. But of course, your result depends on your time of day.

Ok. Wait a moment… Now, you are probably wondering why it is called B. Don’t ask ๐Ÿ˜‰ In fact, it is not the most intuitive name for this type of format. But maybe the following table resolves all the doubts. It is a fragment of pattern letters and symbols handled by the DateTimeFormatter. I guess that B was the first free letter. But of course, I could be wrong.

java-features-time-api

3. Stamped Lock

In my opinion, Java Concurrent is one of the most interesting Java packages. And at the same, one of the less known by the developers, especially if they work mostly with the web frameworks. How many of you have ever used locks in Java? Locking is a more flexible thread synchronization mechanism than the synchronized block. Starting from Java 8 you may use a new kind of lock called StampedLock. StampedLock is an alternative to using a ReadWriteLock. It allows optimistic locking for the read operations. Also, it has better performance than the ReentrantReadWriteLock.

Let’s say we have two threads. The first of them update a balance, while the second of them reads the current value of balance. In order to update the balance, we of course need to read its current value first. We need some kind of synchronization here, assuming the first thread is running multiple times at the same time. The second thread just illustrates how to use an optimistic lock for the read operation.

StampedLock lock = new StampedLock();
Balance b = new Balance(10000);
Runnable w = () -> {
   long stamp = lock.writeLock();
   b.setAmount(b.getAmount() + 1000);
   System.out.println("Write: " + b.getAmount());
   lock.unlockWrite(stamp);
};
Runnable r = () -> {
   long stamp = lock.tryOptimisticRead();
   if (!lock.validate(stamp)) {
      stamp = lock.readLock();
      try {
         System.out.println("Read: " + b.getAmount());
      } finally {
         lock.unlockRead(stamp);
      }
   } else {
      System.out.println("Optimistic read fails");
   }
};

Now, let’s just test it by running both threads 50 times simultaneously. It should work as expected – the final value of the balance is 60000.

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 50; i++) {
   executor.submit(w);
   executor.submit(r);
}

4. Concurrent Accumulators

Locks are not the only one interesting features in Java Concurrent package. Another one is called concurrent accumulators. There are also concurrent adders, but it is a pretty similar functionality. LongAccumulator (there is also DoubleAccumulator) updates a value using a supplied function. It allows us to implement a lock-free algorithm in a number of scenarios. It is usually preferable to AtomicLong when multiple threads update a common value.

Let’s see how it works. In order to create it, you need to set two arguments in the constructor. The first them is a function used to calculate the result of accumulation. Usually, you will the sum method. The second parameter indicates the initial value of our accumulator.

Now, let’s create the LongAccumulator with the initial value 10000 and then invoke the accumulate() method from multiple threads. What’s is the final result? If you think for a moment about it, we did exactly the same thing as in the previous section. But this time without any locks.

LongAccumulator balance = new LongAccumulator(Long::sum, 10000L);
Runnable w = () -> balance.accumulate(1000L);

ExecutorService executor = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
   executor.submit(w);
}

executor.shutdown();
if (executor.awaitTermination(1000L, TimeUnit.MILLISECONDS))
   System.out.println("Balance: " + balance.get());
assert balance.get() == 60000L;

5. Hex Format

There is no big story behind it. Sometimes we need to convert between hex-format strings, bytes, or chars. Since Java 17 you can use the HexFormat class. Just create an instance of HexFormat and then you can format e.g. an input byte table into the hex string. You can also e.g. parse input hex string to the byte table as shown below.

HexFormat format = HexFormat.of();

byte[] input = new byte[] {127, 0, -50, 105};
String hex = format.formatHex(input);
System.out.println(hex);

byte[] output = format.parseHex(hex);
assert Arrays.compare(input, output) == 0;

6. Binary Search for Arrays

Letโ€™s say we want to insert a new element into the sorted table. The Arrays.binarySearch() returns an index of the search key, if contained. Otherwise, it returns an insertion point, which we can use to count an index for a new key: -(insertion point)-1. Moreover, the binarySearch method is the simplest and most efficient method to find an element in a sorted array in Java.

Let’s consider the following example. We have an input table with four elements ordered ascending. We would like to insert the number 3 into this table. Here’s how we can count the index of the insertion point.

int[] t = new int[] {1, 2, 4, 5};
int x = Arrays.binarySearch(t, 3);

assert ~x == 2;

7. Bit Set

What if we need to perform some operations on arrays of bits? You will use boolean[] for that? There is a more effective and memory-efficient method to achieve it. It is the BitSet class. The BitSet class allows us to store and manipulate arrays of bits. In comparison to boolean[] it consumes 8x less memory. We may perform logical operations on arrays such as e.g. and, or, xor.

Let’s say we have two input arrays of bits. We would like to perform the xor operation on them. To clarify, the operation that returns just those elements, which are inserted only to a single array, but not to another one. In order to do that we need to create two instances of BitSet and insert example elements there as shown below. Finally, you should invoke the xor method on one of the BitSet instances. It takes the second BitSet instance as the argument.

BitSet bs1 = new BitSet();
bs1.set(0);
bs1.set(2);
bs1.set(4);
System.out.println("bs1 : " + bs1);

BitSet bs2 = new BitSet();
bs2.set(1);
bs2.set(2);
bs2.set(3);
System.out.println("bs2 : " + bs2);

bs2.xor(bs1);
System.out.println("xor: " + bs2);

Here’s the result after running the code visible above.

8. Phaser

Finally, the last interesting Java feature in this article. The same as some other examples here, it is also the element of the Java Concurrent package. It is called Phaser. It is quite similar to the better-known CountDownLatch. However, it provides some additional features. It allows us to set the dynamic number of threads that need to wait before continuing execution. With Phaser the defined number of threads need to wait on the barrier before going to the next step of execution. Thanks to that we can coordinate multiple phases of execution.

In the following example, we set a barrier of 50 threads to arrive before going to the next phase of execution. Then we create a thread that invokes the method arriveAndAwaitAdvance() on the Phaser instance. It blocks the thread until all the 50 threads won’t arrive to the barrier. Then it goes to the phase-1 and also invokes the method arriveAndAwaitAdvance().

Phaser phaser = new Phaser(50);
Runnable r = () -> {
   System.out.println("phase-0");
   phaser.arriveAndAwaitAdvance();
   System.out.println("phase-1");
   phaser.arriveAndAwaitAdvance();
   System.out.println("phase-2");
   phaser.arriveAndDeregister();
};

ExecutorService executor = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
   executor.submit(r);
}

Here’s the result after executing the code visible above.

java-features-phaser

14 COMMENTS

comments user
wind57

I would agree that anything in java.util.concurrent is amazing. The only problem with this package it time, properly understanding a certain class and its usages takes a huge amount of time. I once spent a few good weeks to understand the source code for LongAdder; it was fun, but painful ๐Ÿ™‚ Otherwise, a great article, thank you for sharing.

    comments user
    piotr.minkowski

    Yes, you are right. Concurrency or security are examples of elements, which may be problematic to understand in the beginning.

comments user
Steve Prior

Am I missing something or for DelayQueue the methods add(E e), offer(E e), offer(E e, long timeout, TimeUnit unit), put(E e) are all functionally identical?

    comments user
    piotr.minkowski

    I would rather say they are all very similar ๐Ÿ™‚

    public boolean add(E e) {
    return offer(e);
    }

    Here’s the put method:
    public void put(E e) {
    offer(e);
    }

    And finally offer:
    public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    q.offer(e);
    if (q.peek() == e) {
    leader = null;
    available.signal();
    }
    return true;
    } finally {
    lock.unlock();
    }
    }

comments user
asdf

Interesting post! I was not aware of a lot of these, mostly because, like you pointed out, web devs seldom use these. Which brings me to my question: have you worked on any applications that do make use of these? Just wondering what kinds of use cases some of these might have? Thanks for sharing this article!

    comments user
    piotr.minkowski

    Personally, I used concurrent accumulators, delay queues, and binary searches. The rest of them just for fun and know understand how it works

comments user
asdffdsa10no

good blog!

    comments user
    piotr.minkowski

    Thanks ๐Ÿ™‚

comments user
michaล‚

Awesome article, but i was wondering what about synchronisation between threads in different nodes of the same java app e.g. ECS nodes? Have you ever struggled with such a problem?

    comments user
    piotr.minkowski

    Thanks ๐Ÿ™‚ To be honest, I didn’t do anything on ECS, so I don’t feel competent in this area…

comments user
ak

1. Delay Queue – may be use new Date-n-Time API instead of old System.currentTimeMillis();
See https://gist.github.com/ak-git/0edf178e5564005a82702f0e6fd0685d

    comments user
    piotr.minkowski

    Yes. Thanks the tip ๐Ÿ™‚

comments user
HyperTesto

DelayQueue seems a perfect fit for a project i’m starting. Thanks for the share!

    comments user
    piotr.minkowski

    No problem ๐Ÿ™‚

Leave a Reply