Better understanding of racing conditions

I looked at the Oracle tutorial on concurrency and streaming description and came across the following lines:

Warning: When building an object that will be used in conjunction with streams, be very careful that the reference to the object does not leak out prematurely. For example, suppose you want to maintain a list called instances containing all instances of the class. You might be tempted to add the following construct to your constructor: instance.add (this); But then other threads can use instances to access the object until the object completes.

Can someone explain what these lines mean?

+4
source share
5 answers

Imagine you have two threads. left thread and right thread. the left thread is responsible for creating work objects, and the right thread is responsible for their work.

when the left thread has finished creating the object, it places the object in a place where the correct thread can find it. this is a variable, let it be called w (for the worker), and for simplicity let me say that it is somehow accessible globally.

the right thread is a loop. he checks if w is not empty. if w actually has any null values, then the do method is called on it.

the working class is as follows:

 public class Worker { private int strength; private float speed; private String name; private String specialty; public Worker(int str, float spd, String n, String spc) { strength = str; speed = spd; name = n; specialty = spc; } public void do() { System.out.println("Worker " + name + " performs " + strength + " " + specialty + " at " + speed + " times per minute."); } } 

so it looks something like this (I tried to illustrate the two threads by setting their respective commands in one column each. I hope this is clear. Remember that only one thread is active at once, so there are always only instructions in one column at a time)

 left thread | right thread ----------------------------------------------------|----------------------------------------------------- | Worker newWorker = new Worker( | ...right thread sleeps... 4, | . 5.2f, | . "mmayilarun", | . "multithreaded programming" | . ); | . -- control jumps to constructor of Worker -- | . strength = str | . speed = spd; | !!right thread wakes up and takes the focus!! | | if(w == null) { | Thread.sleep(500); | } else { | //this doesn't happen, since w is still null | } | | ...right thread goes back to sleep... name = n; | . specialty = spc; | . -- control exits constructor of Worker -- | . w = newWorker; | !!right thread wakes up and takes the focus!! | | if(w == null) { | //this doesn't happen, since w is NOT null anymore | } else { | w.do(); | } | 

everything is fine in this scenario. set of left threads w after completion of the constructor of the worker. but isn't it stupid to do so? imagine the savings we can make if we put this w = instanceOfWorker call inside a working constructor. then we would not have to worry about remembering the actual value of w.

The new worker constructor is as follows:

 public Worker(int str, float spd, String n, String spc) { w = this; strength = str; speed = spd; name = n; specialty = spc; } 

Now the code stream may look like this:

 left thread | right thread ----------------------------------------------------|----------------------------------------------------- | Worker newWorker = new Worker( | ...right thread sleeps... 4, | . 5.2f, | . "mmayilarun", | . "multithreaded programming" | . ); | . -- control jumps to constructor of Worker -- | . w = this; // danger!! | . strength = str; | . speed = spd; | !!right thread wakes up and takes the focus!! | | if(w == null) { | //this doesn't happen, since w is NOT null at this point | } else { | w.do(); //here, w is not yet a fully initialized object, | //and the output is not the expected (if it works at all) | } | | ...right thread goes back to sleep... name = n; | . specialty = spc; | . -- control exits constructor of Worker -- | 

oracle has a more complex example with a set of objects called "instances". that is the only difference.

+4
source

This applies to racing conditions in multi-threaded environments. Another thread may start using an instance of the class before completing the constructor or before the various field values ​​are synchronized, which means that the default values ​​will be displayed by other threads even after they seem to have been initialized in the constructor. This is the same issue with double check locking and other sharing issues.

Here is a good read:


Consider the following constructor (partially copied from an IBM article):

 public class EventListener { private int field; public EventListener(EventSource eventSource) { // do our initialization field = 10; // register ourselves with the event source eventSource.registerListener(this); } public onEvent(Event e) { // what is printed here? System.out.println("Field = " + field); } } 

Due to Java optimization and reordering of commands, there is no guarantee that the field = 10 ends before registerListener(this) . Since events are most likely handled by another thread, you cannot determine if onEvent println field prints as 0 (the default value) or 10.

And even if we are sure that the assignment happened before the registerListener called, because the events are being processed in another thread, and EventListener no EventListener synchronization, then the value may be 0 in this thread even if it is 10 in the thread that called the constructor.

Only if field was defined as final will order be guaranteed. Otherwise, you need to somehow synchronize the EventListener or use the volatile or AtomicInteger .

+4
source

This is a concurrency problem, if there are 2 or more threads active and calling the same code and using the same variables, it is possible that your constructor or general code is accessing 2 threads at the same time. The problem is that 1 thread is expecting something, and because of another thread, a different result is obtained.

+1
source

This means that you should not leak the reference to the object until the constructor completes. For example, see the following code:

 // Constructor public SomeObject() { // A holder class that external to this class. ObjectInstances.LIST.add(this); // Further code constructing the object, setting fields, etc. } 

In this example, the link to the newly created SomeObject instance SomeObject made available to other code, adding it to the external list. This allows other threads to possibly access the object before the constructor completes, so you can access the "incomplete" object.

0
source

This means that if you have a constructor:

 class Bob { List<Bob> instances = ... public Bob(){ instances.add(this); //is bad } public void do Operation(){ ... } } 

This is problematic because if you have multiple threads (for example, you have 1 Bob object / thread), if in the instance of Bob 1 you are in the ctor executing instances.add(this) Then in the instance of Bob 2 you will see Bob in instances . If in Bob 2 you try to execute instance.get(i).doOperation(); , then it will fail because instance 1 of Bob is not fully created, but it is already in the instances variable.

0
source

Source: https://habr.com/ru/post/1401436/


All Articles