Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does @synchronized guarantees for thread safety or not?

With reference to this answer, I am wondering is this correct?

@synchronized does not make any code "thread-safe"

As I tried to find any documentation or link to support this statement, for no success.

Any comments and/or answers will be appreciated on this.

For better thread safety we can go for other tools, this is known to me.

like image 492
Anoop Vaidya Avatar asked Mar 13 '13 17:03

Anoop Vaidya


People also ask

How can you tell if an object is thread-safe?

A method will be thread safe if it uses the synchronized keyword in its declaration. However, even if your setId and getId methods used synchronized keyword, your process of setting the id (if it has not been previously initialized) above is not. .. but even then there is an "it depends" aspect to the question.

Is synchronized method thread-safe?

Thread safe means: method becomes safe to be accessed by multiple threads without any problem at the same time. synchronized keyword is one of the way to achieve 'thread safe'. But Remember:Actually while multiple threads tries to access synchronized method they follow the order so becomes safe to access.

What Happens If not thread-safe?

Conditionally safe: Different threads can access different objects simultaneously, and access to shared data is protected from race conditions. Not thread safe: Data structures should not be accessed simultaneously by different threads.

What is thread-safety how do you achieve it?

Unlike their synchronized counterparts, concurrent collections achieve thread-safety by dividing their data into segments. In a ConcurrentHashMap, for example, several threads can acquire locks on different map segments, so multiple threads can access the Map at the same time.


2 Answers

@synchronized does make code thread safe if it is used properly.

For example:

Lets say I have a class that accesses a non thread safe database. I don't want to read and write to the database at the same time as this will likely result in a crash.

So lets say I have two methods. storeData: and readData on a singleton class called LocalStore.

- (void)storeData:(NSData *)data  {       [self writeDataToDisk:data];  }   - (NSData *)readData  {      return [self readDataFromDisk];  } 

Now If I were to dispatch each of these methods onto their own thread like so:

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{       [[LocalStore sharedStore] storeData:data];  });  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{       [[LocalStore sharedStore] readData];  }); 

Chances are we would get a crash. However if we change our storeData and readData methods to use @synchronized

 - (void)storeData:(NSData *)data  {      @synchronized(self) {        [self writeDataToDisk:data];      }  }   - (NSData *)readData  {       @synchronized(self) {       return [self readDataFromDisk];      }  } 

Now this code would be thread safe. It is important to note that if I remove one of the @synchronized statements however the code would no longer be thread safe. Or if I were to synchronize different objects instead of self.

@synchronized creates a mutex lock on the object you are syncrhonizing. So in other words if any code wants to access code in a @synchronized(self) { } block it will have to get in line behind all previous code running within in that same block.

If we were to create different localStore objects, the @synchronized(self) would only lock down each object individually. Does that make sense?

Think of it like this. You have a whole bunch of people waiting in separate lines, each line is numbered 1-10. You can choose what line you want each person to wait in (by synchronizing on a per line basis), or if you don't use @synchronized you can jump straight to the front and skip all the lines. A person in line 1 doesn't have to wait for a person in line 2 to finish, but the person in line 1 does have to wait for everyone in front of them in their line to finish.

like image 100
Jack Freeman Avatar answered Sep 24 '22 10:09

Jack Freeman


I think the essence of the question is:

is the proper use of synchronize able to solve any thread-safe problem?

Technically yes, but in practice it's advisable to learn and use other tools.


I'll answer without assuming previous knowledge.

Correct code is code that conforms to its specification. A good specification defines

  • invariants constraining the state,
  • preconditions and postconditions describing the effects of the operations.

Thread-safe code is code that remains correct when executed by multiple threads. Thus,

  • No sequence of operations can violate the specification.1
  • Invariants and conditions will hold during multithread execution without requiring additional synchronization by the client2.

The high level takeaway point is: thread-safe requires that the specification holds true during multithread execution. To actually code this, we have to do just one thing: regulate the access to mutable shared state3. And there are three ways to do it:

  • Prevent the access.
  • Make the state immutable.
  • Synchronize the access.

The first two are simple. The third one requires preventing the following thread-safety problems:

  • liveness
    • deadlock: two threads block permanently waiting for each other to release a needed resource.
    • livelock: a thread is busy working but it's unable to make any progress.
    • starvation: a thread is perpetually denied access to resources it needs in order to make progress.
  • safe publication: both the reference and the state of the published object must be made visible to other threads at the same time.
  • race conditions A race condition is a defect where the output is dependent on the timing of uncontrollable events. In other words, a race condition happens when getting the right answer relies on lucky timing. Any compound operation can suffer a race condition, example: “check-then-act”, “put-if-absent”. An example problem would be if (counter) counter--;, and one of several solutions would be @synchronize(self){ if (counter) counter--;}.

To solve these problems we use tools like @synchronize, volatile, memory barriers, atomic operations, specific locks, queues, and synchronizers (semaphores, barriers).

And going back to the question:

is the proper use of @synchronize able to solve any thread-safe problem?

Technically yes, because any tool mentioned above can be emulated with @synchronize. But it would result in poor performance and increase the chance of liveness related problems. Instead, you need to use the appropriate tool for each situation. Example:

counter++;                       // wrong, compound operation (fetch,++,set) @synchronize(self){ counter++; } // correct but slow, thread contention OSAtomicIncrement32(&count);     // correct and fast, lockless atomic hw op 

In the case of the linked question you could indeed use @synchronize, or a GCD read-write lock, or create a collection with lock stripping, or whatever the situation calls for. The right answer depend on the usage pattern. Any way you do it, you should document in your class what thread-safe guarantees are you offering.


1That is, see the object on an invalid state or violate the pre/post conditions.

2For example, if thread A iterates a collection X, and thread B removes an element, execution crashes. This is non thread-safe because the client will have to synchronize on the intrinsic lock of X (synchronize(X)) to have exclusive access. However, if the iterator returns a copy of the collection, the collection becomes thread-safe.

3Immutable shared state, or mutable non shared objects are always thread-safe.

like image 35
Jano Avatar answered Sep 21 '22 10:09

Jano