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

299Synchronizing on Value-Based Classes

Author: Dr Heinz M. KabutzDate: 2022-03-31Java Version: 17Category: Concurrency
 

Abstract: Locking on Integer objects has always been a bad idea. And we now have a way to find such bad code with a runtime switch DiagnoseSyncOnValueBasedClasses.

 

Welcome to the 299th edition of The Java(tm) Specialists' Newsletter, written and conceived in Greece, the birthplace of democracy. A couple of years ago, we were having a delicious birthday cake to celebrate our oldest daughter's 19th, when our son piped up: "Hey, we should get a pony." Being the ever practical, I immediately shot down the idea, listing all the reasons why this could not possibly work. Where would he sleep? What would he eat? Who would clean his stable every morning? After thinking about it for a few days, I realized that I had been a bit of an ass, close relative of said pony. We did have enough space for the critter, and maybe our youngest could one day go on pony rides with her friends? But I had no idea just how stubborn such an animal could be. He got into his head that our driveway was lava. With every fibre in his young body, he resisted any efforts to get him to venture off our lawns. We tried pulling him. Then we tried pushing him. We offered carrots, apples and sugar lumps, which he greedily munched down before jumping back onto the grass. The more we strained, the more he dug in his little hooves. Across the driveway was freedom, but he fought it as if his life depended on it. Eventually my son said: "Why don't we put down a green carpet?" We found an old abandoned olive harvesting net, and spread it across the driveway in the place where he was to cross. Apples coaxed him right to the edge, and with a bit of a tug, he hopped onto the green net. Amazingly, to him at least, he didn't instantly explode in a ball of flames. The driveway wasn't lava after all! After about a week of getting him used to the net, we now have him proudly trotting up and down the paving. I am pretty sure we will get some fun stories out of him, as we explore the countryside together. We are quite an odd couple wandering around Tersanas, these two large round creatures, with wild hair, and mulish stubbornness.

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

Synchronizing on Value-Based Classes

We can synchronize on any Java object. This is thus perfectly legal Java code:

synchronized (new Object()) {
  System.out.println("I have a monitor - but what for?");
}

It is (mostly) pointless, because synchronized is used primarily for mutual exclusion. By making a new object every time, each thread would be synchronizing on their own monitor.

The BankAccount is even more bizarre. It violates several best-practices. Firstly, we are synchronizing on a non-final field, a definite no-no. Secondly, that field is changing every time we deposit or withdraw money, thus the lock also changes. It is not thread-safe at all.

// NOT thread-safe
public class BankAccount {
  private Double balance;

  public BankAccount(double balance) {
    this.balance = balance;
  }

  public void deposit(double amount) {
    synchronized (balance) { // never lock on primitive wrapper!
      balance += amount;
    }
  }

  public void withdraw(double amount) {
    deposit(-amount);
  }

  public double getBalance() {
    synchronized (balance) {
      return balance;
    }
  }
}

Our BankAccountDemo demonstrates the race condition:

import java.util.stream.*;

public class BankAccountDemo {
  public static void main(String... args) {
    var account = new BankAccount(1000);
    IntStream.range(0, 1_000_000)
        .parallel()
        .forEach(i -> {
          account.deposit(100);
          account.withdraw(100);
        });
    System.out.println(account.getBalance());
  }
}

With output something like:

387400.0

It was always considered a bad idea to synchronize on the primitive wrapper classes. This has now been formalized with the @ValueBased annotation. According to the Javadocs, these are the requirements (quoted verbatim):


Value-based Classes

Some classes, such as java.lang.Integer and java.time.LocalDate, are value-based. A value-based class has the following properties:

  • the class declares only final instance fields (though these may contain references to mutable objects);
  • the class's implementations of equals(), hashCode(), and toString() compute their results solely from the values of the class's instance fields (and the members of the objects they reference), not from the instance's identity;
  • the class's methods treat instances as freely substitutable when equal, meaning that interchanging any two instances x and y that are equal according to equals() produces no visible change in the behavior of the class's methods;
  • the class performs no synchronization using an instance's monitor;
  • the class does not declare (or has deprecated any) accessible constructors;
  • the class does not provide any instance creation mechanism that promises a unique identity on each method call—in particular, any factory method's contract must allow for the possibility that if two independently-produced instances are equal according to equals(), they may also be equal according to ==;
  • the class is final, and extends either Object or a hierarchy of abstract classes that declare no instance fields or instance initializers and whose constructors are empty.

When two instances of a value-based class are equal (according to equals()), a program should not attempt to distinguish between their identities, whether directly via reference equality or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism.

Synchronization on instances of value-based classes is strongly discouraged, because the programmer cannot guarantee exclusive ownership of the associated monitor.

Identity-related behavior of value-based classes may change in a future release. For example, synchronization may fail.


Value-based classes are not only good for improving our coding style, they serve as a preparation for Project Valhalla, where we will have, amongst other things, primitive classes. See also JEP 390: Warnings for Value-Based Classes.

The following classes are annotated with the @ValueBased annotation in Java 17:

  • java.lang: Primitive wrappers Boolean, Byte, Character, Double, Float, Integer, Long, Short, and also ProcessHandle, ProcessHandleImpl, Runtime.Version
  • java.time: Duration, Instant, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Period, Year, YearMonth, ZonedDateTime, ZoneId, ZoneOffset
  • java.time.chrono: HijrahDate, JapaneseDate, MinguoDate,ThaiBuddhistDate
  • java.util: the immutable collections produced by List.of(), Set.of(), Map.of(), plus all the Optional classes Optional, OptionalDouble, OptionalLong, OptionalInt

The only value-based classes with public constructors are the primitive wrapper classes Long, Integer, Character, etc. However, these constructors have been deprecated for removal.

MonitorInfo

We can find out whether a thread is currently locking with synchronized. The ThreadMXBean provides ThreadInfo objects on which we can get information about the locks using getLockedMonitors(). Our convenience class Monitors class finds the MonitorInfo objects on the current thread:

import java.lang.management.*;

public class Monitors {
  private static final ThreadMXBean tmb =
      ManagementFactory.getThreadMXBean();

  private Monitors() {}

  public static MonitorInfo[] findLockedMonitors() {
    long[] ids = {Thread.currentThread().getId()};
    var threadInfo = tmb.getThreadInfo(ids, true, false)[0];
    return threadInfo.getLockedMonitors();
  }
}

Here is a demo of how that could be used:

import java.lang.management.*;
import java.util.*;

public class MonitorsDemo {
  public static void main(String... args) {
    printInfo();

    Object lock1 = new Object();
    synchronized (lock1) {
      printInfo();
    }

    Object lock2 = new Object();
    synchronized (lock1) {
      synchronized (lock2) {
        printInfo();
      }
    }
  }

  private static void printInfo() {
    System.out.println("Monitors locked by current thread:");
    MonitorInfo[] monitors = Monitors.findLockedMonitors();
    if (monitors.length == 0) System.out.println("\tnone");
    else Arrays.stream(monitors)
        .forEach(monitor -> System.out.println("\t" + monitor));
    System.out.println();
  }
}

The output is going to be something like this:

Monitors locked by current thread:
	none

Monitors locked by current thread:
	java.lang.Object@776ec8df

Monitors locked by current thread:
	java.lang.Object@58372a00
	java.lang.Object@776ec8df

Note that we do not get access to the objects that we are locking on, but rather, its type (e.g. java.lang.Object) and its identity hash code.

-XX:DiagnoseSyncOnValueBasedClasses=0..2

In the value-based documentation above, it states clearly that we should not synchronize on these types of classes. The objects may be shared instances, and thus there is no guarantee that we will have exclusive access to them. Locking on the shared instances could introduce bad contention or deadlocks. So how can we find whether our code, or any library that we are using, synchronizes on value-based objects?

Fortunately the JVM has a diagnostic setting that allows us to find them at runtime. Simply start your Java Virtual Machine with -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=2 and it will log whenever we try to synchronize on a value-based class. For example:

import java.lang.management.*;
import java.time.*;

// Use these JVM flags:
// -XX:+UnlockDiagnosticVMOptions
// -XX:DiagnoseSyncOnValueBasedClasses=0..2
public class LockingOnValueBasedClasses {
  public static void main(String... args) {
    synchronize(42);

    synchronize("Hello");

    synchronize(LocalDate.now());
  }

  private static void synchronize(Object o) {
    synchronized (o) {
      System.out.println("We now hold the lock of " + o);
      for (MonitorInfo monitor : Monitors.findLockedMonitors()) {
        System.out.println("\t" + monitor);
      }
    }
  }
}

Running this code without any flags, we see:

We now hold the lock of 42
  java.lang.Integer@56a567a2
We now hold the lock of Hello
  java.lang.String@3b9a45b3
We now hold the lock of 2022-03-31
  java.time.LocalDate@5f184fc6

When we run this with -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=2, we see the following output:

[0.071s][info][valuebasedclasses] Synchronizing on object 0x00000007ff8788b0 of klass java.lang.Integer
[0.071s][info][valuebasedclasses] 	at LockingOnValueBasedClasses.synchronize(LockingOnValueBasedClasses.java:17)
[0.071s][info][valuebasedclasses] 	- locked <0x00000007ff8788b0> (a java.lang.Integer)
[0.071s][info][valuebasedclasses] 	at LockingOnValueBasedClasses.main(LockingOnValueBasedClasses.java:9)
We now hold the lock of 42
  java.lang.Integer@56a567a2
We now hold the lock of Hello
  java.lang.String@3b9a45b3
[0.120s][info][valuebasedclasses] Synchronizing on object 0x000000043fcb59e8 of klass java.time.LocalDate
[0.120s][info][valuebasedclasses] 	at LockingOnValueBasedClasses.synchronize(LockingOnValueBasedClasses.java:17)
[0.120s][info][valuebasedclasses] 	- locked <0x000000043fcb59e8> (a java.time.LocalDate)
[0.120s][info][valuebasedclasses] 	at LockingOnValueBasedClasses.main(LockingOnValueBasedClasses.java:13)
We now hold the lock of 2022-03-31
  java.time.LocalDate@5f184fc6

Furthermore, if we run it with -XX:DiagnoseSyncOnValueBasedClasses=1, the JVM crashes the first time that it encounters a synchronized on a value-based class:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (synchronizer.cpp:397), pid=988, tid=6403
#  fatal error: Synchronizing on object 0x00000007ff8788b0 of
  klass java.lang.Integer at LockingOnValueBasedClasses.synchronize(LockingOnValueBasedClasses.java:17)
# etc.

What about String?

We should strongly discourage synchronizing on String objects. Constant strings can be shared throughout the JVM, causing the same issues as we can imagine with value-based classes. However, there are several characteristics that disqualify String from being a @ValueBased class:

  • String has a non-final fields hash and hashCode, used to cache the O(n) hashCode() result;
  • String has 13 public constructors;

Kind regards

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...