I understand that for a primitive type, volatile can immediately reflect changes in values ββfrom another thread
You misunderstand in at least three ways. You should not try to use volatile until you understand all about weak memory models, get and release semantics, and how they affect your program.
First of all, make sure volatile affects variables , not values .
Secondly, volatile does not affect variables that contain values ββof value types in a different way than variables that contain references.
Thirdly, volatile does not mean that a change in value from other threads is immediately visible. . Volatile means that variables acquire and release semantics. Volatile affects the order in which side effects of memory mutations from a specific thread may occur. The idea that there is a consistent universal order of mutations and that these mutations in this order can be instantly observed from all threads is not a guarantee of a memory model.
However, what about the contents of the object?
How about this? The storage location referenced by a variable of a reference type does not need any specific thread characteristics.
If one thread calls the AddValue function, the address of the list does not change whether the other thread will be updated to change the contents of the list.
Nope. Why? This other thread may be on a different processor, and a page containing the address of the array that supports the list may be preloaded in this processor cache. Mutating the list may have changed the location of the store, which contains the address of the array, to link to a completely different location.
Of course, the list class is not thread safe in the first place. If you do not block access to the list, then the list may simply crash and die when you try to do this.
You do not need volatility; you need to set thread locks around access to the list. Since ceiling locks cause full fences, you don't need half fences introduced by volatile ones.