The thread-safety can be expressed like "if the object of the class behaves exactly in the same way in multithreaded environment as in single-threaded environment, then it is thread-safe", or maybe instead "behaves exactly as" we could say "behaves correctly".
In your case correct behavior means that after calling setValue(N), a following getValue() returns N. In case you wouldn't use the volatile modifier, this would be true only in single-threaded environment: if you call setValue(N) on Thread1 then after you call getValue() on Thread2, then it won't necessarily return N. The problem is that Thread1 and Thread2 may be executed on separate CPU cores, and CPU cores usually don't read/write directly the shared memory: the two processors can have separate CPU caches, and the setValue(N) call may modify only the CPU-cached copy of the value member, therefore this change won't be immediately visible for the RAM. Furthermore, even if N is already available in the shared memory, the getValue() may read a cached - but obsolete - value from the second processor cache. Therefore the value change you perform on Thread1 may not be immediately visible on Thread2, so getValue() may return a deprecated value.
The volatile modifier solves it with its following 2 properties:
- after writing a volatile value it won't only be stored in the CPU cache, but immediately flushed into the shared RAM
- reading a volatile value will always read it from the memory, never from the CPU cache.