Creating Object in a thread safety way

2020-05-29 00:49发布

Directly from this web site, I came across the following description about creating object thread safety.

Warning: When constructing an object that will be shared between threads, be very careful that a reference to the object does not "leak" prematurely. For example, suppose you want to maintain a List called instances containing every instance of class. You might be tempted to add the following line to your constructor:

instances.add(this);

But then other threads can use instances to access the object before construction of the object is complete.

Is anybody able to express the same concept with other words or another more graspable example?

Thanks in advance.

8条回答
该账号已被封号
2楼-- · 2020-05-29 01:27

Lot of good data here but I thought I'd add some more information.

When constructing an object that will be shared between threads, be very careful that a reference to the object does not "leak" prematurely.

While you are constructing the object, you need to make sure that there is no way for other threads to access this object before it can be fulling constructed. This means that in a constructor you should not, for example:

  • Assign the object to a static field on the class that is accessible by other threads.
  • Start a thread on the object in the constructor which may start using fields from the object before they are fulling initialized.
  • Publish the object into a collection or via any other mechanisms that allow other threads to see the object before it can be fulling constructed.

You might be tempted to add the following line to your constructor:

   instances.add(this);

So something like the following is improper:

  public class Foo {
      // multiple threads can use this
      public static List<Foo> instances = new ArrayList<Foo>();
      public Foo() {
         ...
         // this "leaks" this, publishing it to other threads
         instances.add(this);
         ...
         // other initialization stuff
      }
      ...

One addition bit of complexity is that the Java compiler/optimizer has the ability to reorder the instructions inside of the constructor so they happen at a later time. This means that even if you do instances.add(this); as the last line of the constructor, this is not enough to ensure that the constructor really has finished.

If multiple threads are going to be accessing this published object, it must be synchronized. The only fields you don't need to worry about are final fields which are guaranteed to be finished constructing when the constructor finishes. volatile fields are themselves synchronized so you don't have to worry about them.

查看更多
Explosion°爆炸
3楼-- · 2020-05-29 01:35

As the thread scheduler can stop execution of a thread at any time (even half-way through a high level instruction like instances.push_back(this)) and switch to executing a different thread, unexpected behaviour can happen if you don't synchronize parallel access to objects.

Look at the code below:

#include <vector>
#include <thread>
#include <memory>
#include <iostream>

struct A {
    std::vector<A*> instances;
    A() { instances.push_back(this); }
    void printSize() { std::cout << instances.size() << std::endl; }
};

int main() {
    std::unique_ptr<A> a; // Initialized to nullptr.

    std::thread t1([&a] { a.reset(new A()); }); // Construct new A.
    std::thread t2([&a] { a->printSize(); }); // Use A. This will fail if t1 don't happen to finish before.

    t1.join();
    t2.join();
}

As the access to a in main()-function is not synchronized execution will fail every once in a while.

This happens when execution of thread t1 is halted before finishing construction of the object A and thread t2 is executed instead. This results in thread t2 trying to access a unique_ptr<A> containing a nullptr.

查看更多
Evening l夕情丶
4楼-- · 2020-05-29 01:46
  1. Let us assume, you have such class:

    class Sync {
        public Sync(List<Sync> list) {
            list.add(this);
            // switch
            // instance initialization code
        }
    
        public void bang() { }
    }
    
  2. and you have two threads (thread #1 and thread #2), both of them have a reference the same List<Sync> list instance.

  3. Now thread #1 creates a new Sync instance and as an argument provides a reference to the list instance:

    new Sync(list);
    
  4. While executing line // switch in the Sync constructor there is a context switch and now thread #2 is working.

  5. Thread #2 executes such code:

    for(Sync elem : list)
        elem.bang();
    
  6. Thread #2 calls bang() on the instance created in point 3, but this instance is not ready to be used yet, because the constructor of this instance has not been finished.

Therefore,

  • you have to be very careful when calling a constructor and passing a reference to the object shared between a few threads
  • when implementing a constructor you have to keep in mind that the provided instance can be shared between a few threads
查看更多
时光不老,我们不散
5楼-- · 2020-05-29 01:49

Its describing the following situation:

Thread1:
 //we add a reference to this thread
 object.add(thread1Id,this);
 //we start to initialize this thread, but suppose before reaching the next line we switch threads
 this.initialize(); 
Thread2:     
//we are able to get th1, but its not initialized properly so its in an invalid state 
//and hence th1 is not valid
Object th1 = object.get(thread1Id); 
查看更多
beautiful°
6楼-- · 2020-05-29 01:52

Here is your clear example :

Let's say, there is class named House

class House {
    private static List<House> listOfHouse;
    private name;
    // other properties

    public House(){
        listOfHouse.add(this);
        this.name = "dummy house";
        //do other things
    }

 // other methods

}

And Village:

class Village {

    public static void printsHouses(){
         for(House house : House.getListOfHouse()){
               System.out.println(house.getName());
         }
    }
}

Now if you are creating a House in a thread, "X". And when the executing thread is just finished the bellow line,

listOfHouse.add(this); 

And the context is switched (already the reference of this object is added in the list listOfHouse, while the object creation is not finished yet) to another thread, "Y" running,

printsHouses();

in it! then printHouses() will see an object which is still not fully created and this type of inconsistency is known as Leak.

查看更多
We Are One
7楼-- · 2020-05-29 01:52

You just have to make sure, that even, when one thread hasn't initialized the Object, no Thread will access it (and get a NullpointerException).

In this case, it would happen in the constructor (I suppose), but another thread could access that very object between its add to the list and the end of the constructor.

查看更多
登录 后发表回答