Functional Equivalence in Java
Asked Answered
P

7

8

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?

Pavla answered 12/9, 2019 at 6:28 Comment(3)
If you understand the issues that can occur during multi-threading, then you will quickly realize that this class, especially because it is stateless, will yield to no issues whatsoever when used in parallel. The issues are mainly if one thread changes something while another thread is using it. But here, there is nothing for anyone to change in the first place.Thong
@Mohammed different behavior on different threads would be caused by difference in values of instance variables. the only difference in result between a call from thread one or thread two in this case, will be because the process passes different parameters. That is not changed if the class is turned into a singletonPotto
As long as parent classes also don't have any fields, yes.Bromide
C
5

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:

Chemaram answered 12/9, 2019 at 7:0 Comment(0)
I
3

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.

Imparipinnate answered 12/9, 2019 at 6:34 Comment(0)
V
2

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.

Vomit answered 12/9, 2019 at 6:40 Comment(4)
The statement doesn't say "instance fields" though, it just says fields. Your statement still holds because something you call inside the compare method could be doing something similar but still.Churchill
@AmmarAskar: I noticed that the statement just says "fields", but the OP's example of "no fields" includes a static field (viz. INSTANCE), so it's clear that he only means instance fields.Vomit
well yeah, that INSTANCE is part of making the singleton...I don't think you can count thatChurchill
@AmmarAskar: *shrug* Fair enough. The whole thing is pedantic, but OK, I'll update to wrap the map in a nested class . . .Vomit
L
1

Calling the compare method from two threads in parallel is safe (stack confinement). The parameters you pass to the method are stored in that thread's stack, that any other thread cannot access.

An immutable singleton is always recommended. Abstain from creating mutable singletons, as they introduce global state in your application, that is bad.

Edit: If the params passed are mutable object references, then you have to take special care to ensure thread safety.

Loosestrife answered 12/9, 2019 at 6:34 Comment(0)
T
1

Explanation

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

Depends. Multi-threading issues can only occur when one thread is changing something while another thread is using it at the same time. Since the other thread might then not be aware of the changes due to caching and other effects. Or it results in a pure logic bug where the creator did not think about that a thread can be interrupted during an operation.

So when a class is stateless, which you have here, it is absolutely safe to be used in a multi-threaded environment. Since there is nothing for any thread to change in the first place.

Note that this also means that a class is not allowed to use not-thread-safe stuff from elsewhere. So for example changing a field in some other class while another thread is using it.


Example

Here is a pretty classic example:

public class Value {
    private int value;

    public int getValue() {
        return value;
    }

    public void increment() {
        int current = value; // or just value++
        value = current + 1;
    }
}

Now, lets assume both threads call value.increment(). One thread gets interrupted after:

int current = value; // is 0

Then the other starts and fully executes increment. So

int current = value; // is 0
value = current + 1; // is 1

So value is now 1. Now the first thread continues, the expected outcome would be 2, but we get:

value = current + 1; // is 1

Since its current was already computed before the second thread ran through, so it is still 0.

We also say that an operation (or method in this case) is not atomic. So it can be interrupted by the scheduler.

This issue can of course only happen because Value has a field value, so it has a changeable state.

Thong answered 12/9, 2019 at 6:34 Comment(0)
H
1

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

Hydrogenize answered 12/9, 2019 at 6:36 Comment(0)
T
1

YES. It is safe to create a static final object of a class if it has no fields. Here, the Comparator provides functionality only, through its compare(String, String) method.

In case of multithreading, the compare method will have to deal with local variables only (b/c it is from stateless class), and local variables are not shared b/w thread, i.e., each thread will have its own (String, String) copy and hence will not interfere with each other.

Thoria answered 12/9, 2019 at 6:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.