One object, two references
You said:
every thread will have an instance member t of class Abs
we have an instance member for every thread
No, not so.
Each of your two Task1
objects holds a reference to an object of type Abs
, the very same single object. You instantiated only a single Abs
object. Then you passed a reference to each of the new Task1( obj )
calls.
Knowing the difference between an object (a chunk of memory) and a reference (basically, a pointer to that chunk’s location in memory) is crucial here. If you are not clear on this, study the subject.
Since you have only one Abs
object in your example app, there is only one int
primitive variable named x
. So your code is dealing with a single number value. Each of your threads is accessing, and replacing, that value. So your code is not thread-safe.
See also comment by markspace.
Abs obj = new Abs(); // (1) Create one single object. (2) Create a reference to that object. (3) Store that reference in a variable named `obj`.
Task1 t1 = new Task1( obj ); // Pass a *reference* to this constructor.
Task1 t2 = new Task1( obj ); // Pass a *reference* to this constructor. Happens to be the very same reference passed in line above.
// At this point, both `t1` and `t2` hold a reference leading to the same single `Abs` object. One object in memory, two references to that object.

Race condition
The race condition occurs because x++
is not an atomic operation, as discussed here. Three separate operations occur:
- Reading
x
.
- Incrementing for a new value.
- Writing the new value back to
x
.
Being non-atomic means one thread can read the value of that number, and in an intervening moment, a second thread can both read and increment that same number, writing a new value to the x
variable. A moment later, the first thread increments its previously read value, and stores that new value back into x
. (Which threads operate in what order for however amount of time between pauses is completely unpredictable!)
So both threads can read a value of 1
. And both threads can increment that value to 2
. The value 2
can be written twice, once by each of the two threads.
The lesson to learn in concurrency is that any mutable resource shared across threads must be protected. (Simple in concept, tricky in practice.)
AtomicInteger
In your case, one simple way to add that protection is to change your int x
primitive to an AtomicInteger
object. The AtomicInteger
class makes reading, writing, and reporting an integer value atomic.
In Abs
class, change the declaration of x
. And change the incrementing line x++
to call a method on AtomicInteger
instead.
We mark x
as final
to prevent any reassignment to a different AtomicInteger
object. Reassignment would ruin the integrity of our purpose of a rolling counter.
class Abs
{
final AtomicInteger x = new AtomicInteger( 0 ) ;
public void increment ( )
{
int newValue = x.incrementAndGet() ;
System.out.println( newValue );
}
}
A fine point on code above: We instantiate our AtomicInteger
as part of the declaration because this guarantees per the Java specification that the code will be run before any other code has a chance to access this member field. Combined with final
, we know that all threads will see and access the same single reference stored in x
in a thread-safe manner.
By the way, in modern Java we rarely address the Thread
class directly. Generally best to use the Executors framework instead.