Race condition
How to find Race Conditions in Java
Finding race conditions by unit testing is not reliable due to random nature of race conditions. Since race conditions surfaces only some time your unit test may passed without facing any race condition. Only sure shot way to find race condition is reviewing code manually or using code review tools which can alert you on potential race conditions based on code pattern and use of synchronization in Java. Most people solely rely on code review strategy and yet to find a suitable tool for exposing race condition in java.
Code Example of Race Condition in Java
Two code patterns namely “check and act” and “read modify write” can suffer race condition if not synchronized properly. both cases rely on natural assumption that a single line of code will be atomic and execute in one shot which is wrong e.g. ++ is not atomic.
“Check and Act” race condition pattern
classical example of “check and act” race condition in Java is getInstance() method of Singleton Class. getInstace() method first check for whether instance is null and then initialized the instance and return to caller. Whole purpose of Singleton is that getInstance should always return same instance of Singleton. if you call getInstance() method from two thread simultaneously its possible that while one thread is initializing singleton after null check, another thread sees value of _instance reference variable as null (quite possible in java) especially if your object takes longer time to initialize and enters into critical section which eventually results in getInstance() returning two separate instance of Singleton. This may not happen always because a fraction of delay may result in value of _instance updated in main memory. here is a code example
public Singleton getInstance(){
if(_instance == null){
//race condition if two threads sees _instance= null
_instance = new Singleton();
}
}
An easy way to fix “check and act” race conditions is to use synchronized keyword and enforce locking which will make this operation atomic and guarantees that block or method will only be executed by one thread and result of operation will be visible to all threads once synchronized blocks completed or thread exited form synchronized block.
Read-modify-update race conditions
This is another code pattern in Java which cause race condition, classical example is the non thread safe counter. read-modify-update pattern also comes due to improper synchronization of non-atomic operations or combination of two individual atomic operations which is not atomic together e.g. put if absent scenario. Consider below code
if(!hashtable.contains(key)){
hashtable.put(key,value);
}
Here we only insert object into hashtable if its not already there. point is both contains() and put() are atomic but still this code can result in race condition since both operation together is not atomic. consider thread T1 checks for conditions and goes inside if block now CPU is switched from T1 to thread T2 which also checks condition and goes inside if block. now we have two thread inside if block which result in either T1 overwriting T2 value or vice-versa based on which thread has CPU for execution. In order to fix this race condition in Java you need to wrap this code inside synchronized block which makes them atomic together because no thread can go inside synchronized block if one thread is already there
Data race
How Rust handles data races?
The restriction preventing multiple mutable references to the same data at the same time is something that new Rustaceans struggle with because most languages let you mutate whenever you’d like. The benefit of having this restriction is that Rust can prevent data races at compile time.
A data race is similar to a race condition and happens when these three behaviors occur:
- Two or more pointers access the same data at the same time.
- At least one of the pointers is being used to write to the data.
- There’s no mechanism being used to synchronize access to the data.
Data races cause undefined behavior and can be difficult to diagnose and fix when you’re trying to track them down at runtime; Rust prevents this problem by refusing to compile code with data races!
References: https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html