Thread safety of local variables

Viewed 344

Local variables are always thread safe. Keep in mind though, that the object a local variable points to, may not be so. If the object was instantiated inside the method, and never escapes, there will be no problem.

I am new to multi threading in Java, I do not understand what it means by `object escaping the method'. Can anyone please show me an code example for this statement where the object becomes non thread safe by escaping the method. Also please explain why it becomes non thread safe

Does it mean that if we simply pass this object to another method it will become non thread safe?

NB : This may sound like a simple question to many of you. But for me I read multiple articles to see a code sample but couldn't find it.

4 Answers

Does it mean that if we simply pass this object to another method it will become non thread safe?

Yes.

Well, it could become non-thread-safe: it depends what that other method does with it.

A non-thread-safe example would be something like:

class YourClass {
  List<String> field;

  void foo() {
    List<String> local = new ArrayList<>(List.of("foo"));
    unsafe(local);        
  }

  void unsafe(List<String> param) {
    field = param;
  }
}

This is thread-unsafe because other threads can now access the object referred to by the local variable, through the field.

Of course, it can only become thread-unsafe if the value is mutable. An immutable value is inherently thread-safe, so it can be passed around, stored in fields, whatever.

A still-thread-safe example could be something like:

void stillSafe(List<String> param) {
  System.out.println(param);
}

This is still thread-safe (if called with a local variable as a parameter) because param is used in a thread-confined way (nothing outside the current thread of execution can see its value).

You can even mutate it in the method:

void stillSafe2(List<String> param) {
  param.add("Hello!");
}

Despite the mutation, this is still thread-safe, because still only the current thread can see the value.

Java is a language based on references. That's just another word for pointers.

When you write:

String foo = "hello";

That is just syntax sugar for:

String foo; // [1]
foo = "hello"; // [2]

Line 1 declares a variable (a pointer) named 'foo'. Line 2 does two things: It creates an entirely new object. foo is then updated to refer to it. It's like "hello" is the treasure, and foo is a treasure map. foo = "Hello" creates new treasure, buries it in the sand, and then draws a map to the treasure on your piece of paper; the piece of paper you labelled foo. "foo" is not the treasure, and it is incorrect to say that this string is the "foo" string. It's not - it's just treasure, and treasure has no names. There can be no maps that lead to it (which means the garbage collector will eventually dig it up and get rid of it), there can be a thousand maps that lead to it, and anything in between. In the above snippet, there is one map that leads to it. Your map. The one you called foo.

But you can share that map with others. And then they can find your treasure too.

'locals are immutable'. Yes. They are. However, your local is that foo variable, and foo is not the treasure. It's the map. It's your map. Nobody can mess with it. The only way that map is ever going to look any different, is if you write foo = someplace in your own code. No amount of passing foo around to other methods is ever going to change that map.

What the text you read is referring to, is that the only thing that's immutable is your map. The treasure the map is pointing at? Who knows. If you hand your map to others, they can't change your map, but they can copy it. They can follow it. They can dig for that treasure, and smash it to smithereens. They can't affect your map, but if you follow your map, whatever you find there? Could be completely different by now, if you shared the location of that treasure with anything else.

Now, for strings, this is a moot point: String treasures are invincible. They cannot be smashed or modified in any way. They are immutable objects, with no methods that change it, and no public (non-final) fields. But not all objects are like that. Some treasures can be changed or smashed. For example, a simple list:

List<String> x = new ArrayList<String>();
x.add("Hello");
foo(x);
System.out.println(x);

In the above code you have no idea what it prints, because you don't know what foo does. You do know that your x cannot possibly print null. null refers to the idea that you have a blank treasure map, and nobody can mess with your treasure map. But foo CAN mess with the treasure:

public void foo(List<String> x) {
    x = List.of();
}

this does not do anything. This method gets a copy of your treasure map. It then creates new treasure, buries it in the sand, takes an eraser to its x treasure map (which used to hold a copy of yours), and then paints an entirely new treasure map on it, where X marks the location of the newly created treasure. This does absolutely nothing whatsoever to your map, or to the treasure that your map leads to. Your code will continue to print [Hello].

However:

public void foo(List<String> x) {
    x.add("World");
}

This is quite different. This takes the copy of your treasure map that you gave it, follows it and digs down (. is java-ese for: Follow the map and dig). It then opens the chest, and puts "World" in there (technically, it puts a map to the "World" treasure in there, Strings are objects too, thus, reference-based).

If you then later follow your map and dig, you see that. Your code would print [Hello, World].

That's what the text is talking about. Avoid it by making copies, or by working with immutable objects, or by being aware of the notion that any treasures that your maps point at may have been changed by other code if you've shared your maps.

It should not be hard to understand if you look at it piece-by-piece.

If two or more threads in your program access the same data (a.k.a., "shared data") then you need to ensure that your code accesses the data in a "thread-safe" way.

The Java programming language, unlike some other languages such as C++, makes it impossible for threads to share local variables. The only variables that can be shared are static variables, and the member variables (a.k.a., "fields") of shared objects.

Passing an object reference as an argument in a function call does not, in and of itself, allow the object reference to "escape" to another thread. But it could allow it. It all depends on what the called function does with the object that you passed. If you write code that calls a function in a multi-threaded program, it's your responsibility to know what the function will do with the objects that you give it (e.g., whether the function will share the object with another thread or not.)


Some ways an object can become shared:

  • You store a reference to the object into a static variable that is shared by threads,

  • The object is the Runnable object that you give to a new Thread, or it is the referent of one of the Runnable object's member variables,

  • The object is a Runnable or the Callable that you submit to a thread pool, or it is the referent of a member variable of an object that you submit to a thread pool,

  • The object is a referent of a member variable of some other object that previously was shared including, and especially,

    • You add the object to a container that is shared (e.g., a List or a Set or a Map,
    • You put the object into a Queue that was set up especially for sharing objects between threads.

Escaping the method means: it is used or stored somewhere outside the method.

Example: you have a global variable counter and you count something inside your local method. Now as long as you use the local counter everything is fine, but as soon as you set globalcounter = localcounter you can not assume that down the line globalcounter == localcounter, as it can always be changed by an outside factor.

Related