Running on Java 24-ea+21-2447 (Preview)
Home of The JavaSpecialists' Newsletter

218Thread Confinement

Author: Dr. Heinz M. KabutzDate: 2014-02-18Java Version: 7Category: Concurrency
 

Abstract: One of the techniques we use to ensure that a non-threadsafe class can still be used by multiple threads is to give each thread its own instance. We call this "thread confinement". In this newsletter we look at some of the issues that can happen when this instance leaks.

 

Welcome to the 218th issue of The Java(tm) Specialists' Newsletter. If you missed accepting your invitation to our 4th annual JCrete unconference, then I have sad news: we are completely full and have started a waiting list. As in previous years, the combination of sunshine, great food and deep intellectual stimulation proved too tempting to our fellow Java geeks. This year we have participants from 25 nations! Here is again the link to the promotional video, showcasing what JCrete is all about, from interesting discussions about Java to hurling ourselves off treacherous cliffs. Plus delicious Cretan food every day!

I wrote most of this newsletter in my little Suzuki Jimny at Kalathas beach, a bit more inspiring than sitting in a cubicle in a cold grey building. If I was retired, I would probably have spent my morning exactly the way I did. BTW, we call this "winter":

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Thread Confinement

After studying Brian Goetz's excellent Java Concurrency in Practice book over a period of about 10 months in order to write my Concurrency Specialist Course, I came to the conclusion that two of the most useful tricks for ensuring thread safety are "stack confinement" and "thread confinement". In "stack confinement", we ensure that an object never escapes from a method. In "thread confinement", we only ever see a particular object from a single thread. Even if the object is not threadsafe, it now does not matter, since it is unshared.

Unfortunately there is no language support for enforcing "thread confined" objects. Let's start with a classic example of the SimpleDateFormat. It is one of the most tempting objects in the JDK. Typically, we would begin by having it "stack confined", such as:

public String stackConfined(Date date) {
  DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
  return df.format(date);
}

After calling this method many times, we might discover that it is quite expensive to construct a new SimpleDateFomat object every time and thus be tempted to do the following:

private static final DateFormat df =
    new SimpleDateFormat("yyyy-MM-dd");
public String broken(Date date) {
  return df.format(date);
}

Of course, now the object is no longer "stack confined" and we will get all sorts of weird dates being returned to us. See also my newsletter on "Wonky Dating". A common trick is to make the object "thread confined", like so:

private static final ThreadLocal<DateFormat> tdf =
  new ThreadLocal<DateFormat>() {
    protected DateFormat initialValue() {
      return new SimpleDateFormat("yyyy-MM-dd");
    }
  };
public String threadConfined(Date date) {
  return tdf.get().format(date);
}

This technique can cause memory leaks with managed threads. See my newsletter 164 for more on ThreadLocal.

ThreadLocalRandom Puzzle

I recently sent this puzzle to the Concurrency-interest mailing list. Before carrying on reading, please try answer the questions: What does this program do in Java 7? What does it do in Java 8?

import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class MagicMirror {
 private static final ThreadLocalRandom tlr =
     ThreadLocalRandom.current();

 public boolean amIPretty() {
   return tlr.nextBoolean();
 }

 public static void main(String... args) {
   final AtomicBoolean vanity = new AtomicBoolean(true);
   while (vanity.get()) {
     new Thread(new Runnable() {
       public void run() {
         MagicMirror mirrorOnTheWall = new MagicMirror();
         boolean beauty = mirrorOnTheWall.amIPretty();
         if (!beauty) vanity.set(false);
       }
     }).start();
   }
   System.out.println("Oh no, now I am depressed!");
 }
}

Before you carry on reading, please make sure that you have thought a bit about the code above. Maybe run it in both Java 7 and 8 to see if it matches your expectations.

Next puzzle. What is the output when we run this code that generates a random message? Try running it a few times in Java 7 and then run it in Java 8:

import javax.swing.*;
import java.awt.*;
import java.util.concurrent.*;

import static javax.swing.WindowConstants.*;

public class MagicMessage {
  private static final ThreadLocalRandom tlr =
      ThreadLocalRandom.current();

  public static void main(String... args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        JFrame frame = new JFrame();
        JLabel label = new JLabel(generateRandomString(),
            SwingConstants.CENTER);
        frame.add(label, BorderLayout.NORTH);
        frame.setSize(300, 100);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
      }
    });
  }

  private static String generateRandomString() {
    char[] randomText = "HVTia\u000EDlciP".toCharArray();
    for (int i = 0; i < randomText.length; i++) {
       randomText[i] += tlr.nextInt(26);
    }
    return new String(randomText);
  }
}

Please try it out before you carry on reading ... :-) It will be worth it :-)

A Word About Random

We would imagine to sometimes get the same value twice in a row when generating random values. For example, when I play Tavli (Greek backgammon, which involve a lot of cheating, tsikoudia and laughter) with my synteknos, we sometimes throw the same dice three or four times in a row. As humans we usually attribute this to something other than chance. If we were asked to manually generate a sequence of random dice, we would probably not have the same two dice appear even twice in sequence. But in real life, this does happen.

I have written before about how slow Math.random() is, especially in a multi-threaded context, and that we should rather use ThreadLocalRandom since Java 7. I also mentioned that the correct way to use it was like so: ThreadLocalRandom.current().nextInt();

However, in Java 7, the random seeds for ThreadLocalRandom were stored inside a ThreadLocal field. Every time we called ThreadLocalRandom.current(), we did a hash map lookup until we found the correct seed value. This meant that if we needed to generate a lot of random values, it was tempting to store the instance somewhere, either inside a method, or perhaps in a field. If we stored it inside a method, then it would be "stack confined" and thus not available to other threads. But if we stored it in a field, then the reference would leak and thus no longer be "thread confined". It would be a mistake to do this.

Let's say we wanted to generate a bunch of random values and fill an array with these, such as this:

import java.util.concurrent.*;

public class RandomArrayFiller {
  public static void main(String... args) {
    int[] numbers = new int[100_000_000];

    for (int i = 0; i < 5; i++) {
      // thread confined
      long time = System.currentTimeMillis();
      fillRandomThreadConfined(numbers);
      time = System.currentTimeMillis() - time;
      System.out.println("Thread Confined took " + time + " ms");

      // stack confined
      time = System.currentTimeMillis();
      fillRandomStackConfined(numbers);
      time = System.currentTimeMillis() - time;
      System.out.println("Stack Confined took " + time + " ms");

      // leaked
      time = System.currentTimeMillis();
      fillRandomLeaked(numbers);
      time = System.currentTimeMillis() - time;
      System.out.println("Leaked took " + time + " ms");
    }
  }

  public static void fillRandomThreadConfined(int[] numbers) {
    for (int i = 0; i < numbers.length; i++) {
      numbers[i] = ThreadLocalRandom.current().nextInt();
    }
  }

  public static void fillRandomStackConfined(int[] numbers) {
    ThreadLocalRandom tlr = ThreadLocalRandom.current();
    for (int i = 0; i < numbers.length; i++) {
      numbers[i] = tlr.nextInt();
    }
  }

  static ThreadLocalRandom tlr = ThreadLocalRandom.current();

  public static void fillRandomLeaked(int[] numbers) {
    for (int i = 0; i < numbers.length; i++) {
      numbers[i] = tlr.nextInt();
    }
  }
}

In Java 7, the fillRandomThreadConfined() is a lot slower than the other two methods. In Java 8, they are closer in speed, but the fillRandomThreadConfined() is still a bit slower. If we ran this fillRandomLeaked() code from multiple threads, we would occasionally get the same value as race conditions occurred. Since we are working out random values anyway, this should not disturb us too much. But in Java 8, the seed values are stored inside the Thread and are initialized lazily when we call the current() method on ThreadLocalRandom. However, if we never call current on the thread, then the seeds stay at the default value, which means each thread would see the same sequence of random values. This explains the strange behaviour in my puzzles above.

I pointed this out to the concurrency interest mailing list, but was told that the fillRandomLeaked() method had broken the contract by storing a ThreadLocalRandom instance and thus deserved to be punished. In a way they are correct, but in my opinion lack an understanding of how programmers work in the real world. To leak the instance into a field is a mistake that is easy to make, since there is no language construct to ensure thread confinement. I understand that they did not want to have too many dependencies between Thread and ThreadLocalRandom, but since the fields are now in Thread anyway, it would make more sense to me to simply do an eager initialization of the seed values. All it would require is an atomic long update. Thread is such an expensive resource to create anyway, that another couple of instructions at start up won't break the camel's back. I am pretty sure that the reasoning to do the initialization lazily was based on managing dependencies, rather than performance.

I tried to persuade the authors of the Java 8 ThreadLocalRandom to either:

  • Initialize the seed values eagerly
  • or, check that the seed has been set in the next() method, rather than in current()
  • or, fail in next(), via exception or assertion, if the seed has not been set.

For now, everything will stay exactly as it is. So please, don't ever leak a ThreadLocalRandom instance, either by storing it in a field or passing it to a method or having it leak accidentally to an anonymous class. Here is an example that will only work in Java 8, as local variables can be "effectively final". In Java 7, it could be more obvious, in that you have to make local variables final if you want to use them from inside an anonymous class (or as in this case, from inside a class defined inside the method). And if you are one of those programmers who have the incredibly annoying habit of marking ALL your local variables and parameters "final", then even in Java 7 it would be hard to see that tlr was being leaked into the instance of RandomFillerTask.

public static void fillRandomParallel(int[] numbers) {
  ThreadLocalRandom tlr = ThreadLocalRandom.current();

  class RandomFillerTask extends RecursiveAction {
    private static final int THRESHOLD = 100_000;
    private final int[] numbers;
    private final int offset;
    private final int length;

    RandomFillerTask(int[] numbers, int offset, int length) {
      this.numbers = numbers;
      this.offset = offset;
      this.length = length;
    }

    protected void compute() {
      if (length < THRESHOLD) {
        for (int i = offset; i < offset + length; i++) {
          numbers[i] = tlr.nextInt();
        }
      } else {
        int offsetLeft = offset;
        int lengthLeft = length / 2;
        int offsetRight = offset + lengthLeft;
        int lengthRight = length - lengthLeft;
        RandomFillerTask left = new RandomFillerTask(
            numbers, offsetLeft, lengthLeft
        );
        RandomFillerTask right = new RandomFillerTask(
            numbers, offsetRight, lengthRight
        );
        invokeAll(left, right);
      }
    }
  }

  ForkJoinPool fjp = new ForkJoinPool();
  fjp.invoke(new RandomFillerTask(numbers, 0, numbers.length));
  fjp.shutdown();
}

Again, I would like to point out that the code above has a bug - we are letting the ThreadLocalRandom instance leak into other threads. But you can hopefully see that this is a mistake that is incredibly easy to make. Imagine every time you pressed the accelerator and break pedals at the same time, your motor exploded? Well, in your car's manual, there might be a warning to never do that. Unless you are a rally driver, why would you want to? But blowing up your motor seems a bit extreme, as does what happens here. Incidentally, no one on the list guessed the correct answer for the MagicMirror class.

Kind regards from Crete - hope to see you on one of our courses here, so we can enjoy it together :-)

Heinz

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...