Java multi-threading involves two problems, ensuring that multiple operations can be done consistently, without mixing actions by different threads, and making a change in a variable's value available to threads other than the on doing the change.
In reality, a variable does not naturally exist at a single location in the hardware. There may be copies in the internal state of different threads, or in different hardware caches. Simply assigning to a variable automatically changes its value from the point of view of the thread doing the assignment.
If the variable is marked "volatile" other threads will get the changed value.
"synchronized" also ensures changes become visible. Specifically, any change done in one thread before the end of a synchronized block will be visible to reads done by another thread in a subsequent block synchronized on the same object.
In addition, blocks that are synchronized on the same object are forced to run sequentially, not in parallel. That allows one to do things like adding one to a variable, knowing that its value will not change between reading the old value and writing the new one. It also allows consistent changes to multiple variables.
The best way I know to learn what is needed to write solid concurrent code in Java is to read Java Concurrency in Practice