Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional Equivalence in Java

I was reading Effective Java, and came across a condition where Joshua Bloch recommends something like

class MyComparator extends Comparator<String>{
    private MyComparator(){}
    private static final MyComparator INSTANCE = new MyComparator();

    public int compare(String s1,String s2){ 
        // Omitted
    }
}

XYZComparator is stateless, it has no fields. hence all instances of the class are functionally equivalent. Thus it should be a singleton to save on unnecessary object creation.

So is it always safe to create a static final Object of whatever class it is pointing to if it has no fields? Wouldn't this cause multithreading issue when compare is called from two threads parallely? Or I misunderstood something basic. Is it like every thread has autonomy of execution if no fields is shared?

like image 939
Mohamed Anees A Avatar asked Sep 12 '19 06:09

Mohamed Anees A


4 Answers

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
like image 164
Andrew Tobilko Avatar answered Nov 04 '22 15:11

Andrew Tobilko


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.

like image 32
Wim Deblauwe Avatar answered Nov 04 '22 16:11

Wim Deblauwe


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.

like image 2
ruakh Avatar answered Nov 04 '22 16:11

ruakh


XYZComparator is stateless, it has no fields. hence all instances of the class are functionally equivalent. Thus it should be a singleton to save on unnecessary object creation.

From that point of view, the "current day" answer is probably: make MyComparator an enum. The JVM guarantees that MyComparatorEnum.INSTANCE will be a true singelton, and you don't have to worry about the subtle details that you have to consider when building singletons "yourself".

like image 1
GhostCat Avatar answered Nov 04 '22 17:11

GhostCat