Trying to reason about the asynchronicity of interactions of threads that occur with multithreading is the hard part of multithreading. If you spin up a bunch of threads that work entirely independently and have no results to deal with, there isn't much thinking you need to do. But that's almost never the case.
There are two basic reasons for using multithreading. One is to simply keep a user interface responsive by doing lengthy operations on a background thread. The other is to make use of multiple CPUs to scale an application, or make it take less time processing. There's kind of a third scenario when it comes to multithreading, that is the recognition of asynchronous operations and the avoidance of blocking a thread to wait for an asynchronous operation.
Each scenario has some slightly different synchronization subtleties, but let's first take a look at some fundamental ways of synchronizing data access across threads.