Functional Equivalence in Java

So is it always safe to create a static final Object of whatever class it is pointing to if it has no fields?

I would dare to say yes. Having no fields makes a class stateless and, thus, immutable, which is always desirable in a multithreading environment.

Stateless objects are always thread-safe.

Immutable objects are always thread-safe.

An excerpt from Java Concurrency In Practice:

Since the actions of a thread accessing a stateless object cannot affect the correctness of operations in other threads, stateless objects are thread-safe.

Stateless objects are always thread-safe.

The fact that most servlets can be implemented with no state greatly reduces the burden of making servlets threadͲ safe. It is only when servlets want to remember things from one request to another that the thread-safety requirement becomes an issue.

...

An immutable object is one whose state cannot be changed after construction. Immutable objects are inherently thread-safe; their invariants are established by the constructor, and if their state cannot be changed, these invariants always hold.

Immutable objects are always thread-safe.

Immutable objects are simple. They can only be in one state, which is carefully controlled by the constructor. One of the most difficult elements of program design is reasoning about the possible states of complex objects. Reasoning about the state of immutable objects, on the other hand, is trivial.


Wouldn't this cause multithreading issue when compare is called from two threads parallelly?

No. Each thread has own stack where local variables (including method parameters) are stored. The thread's stack isn't shared, so there is no way to mess it up parallelly.

Another good example would be a stateless servlet. One more extract from that great book.

@ThreadSafe
public class StatelessFactorizer implements Servlet {
    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        encodeIntoResponse(resp, factors);
    }
}

StatelessFactorizer is, like most servlets, stateless: it has no fields and references no fields from other classes. The transient state for a particular computation exists solely in local variables that are stored on the thread's stack and are accessible only to the executing thread. One thread accessing a StatelessFactorizer cannot influence the result of another thread accessing the same StatelessFactorizer; because the two threads do not share state, it is as if they were accessing different instances.


Is it like every thread has autonomy of execution if no fields is shared?

Each thread has its own program counter, stack, and local variables. There is a term "thread confinement" and one of its forms is called "stack confinement".

Stack confinement is a special case of thread confinement in which an object can only be reached through local variables. Just as encapsulation can make it easier to preserve invariants, local variables can make it easier to confine objects to a thread. Local variables are intrinsically confined to the executing thread; they exist on the executing thread's stack, which is not accessible to other threads.

To read:

  • Java Concurrency In Practice
  • Thread Confinement
  • Stack Confinement using local object reference

Multithreading issues are caused by unwanted changes in state. If there is no state that is changed, there are no such issues. That is also why immutable objects are very convenient in a multithreaded environment.

In this particular case, the method only operates on the input parameters s1 and s2 and no state is kept.


So is it always safe to create a static final Object of whatever class it is pointing to if it has no fields?

"Always" is too strong a claim. It's easy to construct an artificial class where instances are not thread-safe despite having no fields:

public class NotThreadSafe {
    private static final class MapHolder {
        private static final Map<NotThreadSafe, StringBuilder> map =
            // use ConcurrentHashMap so that different instances don't
            // interfere with each other:
            new ConcurrentHashMap<>();
    }

    private StringBuilder getMyStringBuilder() {
        return MapHolder.map.computeIfAbsent(this, k -> new StringBuilder());
    }

    public void append(final Object s) {
        getMyStringBuilder().append(s);
    }

    public String get() {
        return getMyStringBuilder().toString();
    }
}

. . . but that code is not realistic. If your instances don't have any mutable state, then they'll naturally be threadsafe; and in normal Java code, mutable state means instance fields.