To understand the correctness of a given line of lock-based concurrency code, you have to understand the entire program.
Now, you can save on this a bit. If you manage to prove to yourself that everything accessed in that bit of lock-based concurrency code is only accessed within a particular lock, you now only have to understand every bit of code in the program that also accesses that lock, its state when it starts the lock access, and everything it does while holding it.
This is because lock-based concurrency only maintains "thread safety" by locking access to data to limited numbers of threads at once (sometimes 1, sometimes only readers, etc). So you have to understand all of the locking behavior, and behavior while locking, in order to know if you lock system is actually doing what you want it to.
Next, because locks are subject to deadlocks, in order to understand multiple locks you have to understand not only the code in a lock, but every possible sequence you can acquire locks in.
And our languages and type systems do not help all that much with these tasks.
It gets worse. Like the other hard to track down and common errors, the deadlock and race condition problems usually do not happen reliably. You can write the code, do some simple tests, even run it in production, and have it work almost always. Except sometimes it fails spectacularly.
Really rare spectacular failures are hard to debug regardless of how easy they are to reason about. Here we have rare, spectacular failures that are difficult to reason about.
And it gets worse. When learning how to program, people typically run into a number of hurdles. Understanding state/assignment/data flow is a hurdle some don't get over. Understanding conditionals and function calling is another. Recursion and iteration throws some people for a loop. Indirection and pointers cut off an entire set of would-be programmers from becoming useful. Resource management. And concurrency and synchronization is another such hurdle.
We can generate more useful programmers by removing some of these hurdles "in the way" of being productive. There are languages that hide indirection and pointers from the programmer, and handle the big resource management task for you (memory), and you can learn to become a productive programmer in those languages without getting over that hurdle.
In almost every language you can become a "productive programmer" without getting over the concurrency hurdle. You can produce useful code, be a professional developer, graduate from university -- all of them without ever getting concurrency.
The ones that didn't get flow control, data flow and iteration never became professional programmers. The ones who don't get concurrency are productive senior developers at many a company.