In a multi-threaded environment, accessing mutable data (data that can change) must always be
coordinated between readers and writers.
The task of making sure that readers and writers don't interfere with each other in undesirable ways is
called synchronization.
Synchronization can be done with an explicit lock object, but a more common style is to use
the intrinsic locks implied by the synchronized keyword.

For example, in a multi-threaded environment, all get and set methods for
mutable fields should usually be synchronized methods.
This includes primitive fields.

Most classes do not need to be designed for operation in a multi-threaded environment,
and can ignore these considerations.

If an object does need to live in a multi-threaded environment, however, then a significant
amount of care must be taken to ensure that it is correctly designed.

If an object is immutable, then it's automatically
thread-safe. If it's mutable, then extra steps
must be taken to ensure thread-safety: every use of every mutable field
must be synchronized in some way (usually with using the synchronized keyword).

Here, mutable field simply means a field which might change in any way, after the initial construction of the object.
(Objects are never shared between threads until after the object is fully created.)
For example,

an int field that changes its value some time after the constructor
completes

a Date field that changes state some time after the constructor
completes, to represent a different date from the original

any object field that is "pointed" to a new object, some time after the
constructor completes

Remember that all local variables declared in the body of a method
are never shared between threads, and so have no thread-safety considerations.

It's a misconception that all mutable primitives except long
and double do not need synchronized access.