Java’s synchronization can be really helpful, but it can also get you into plenty of trouble. Synchonization is in no way a magic wand that you can wave around to get rid of multi-threading issues, you have to understand how to use it.

In java (and many other languages, but java’s what I’m familiar with), synchronization prevents threads from accessing the same data at the same time. Concurrency (multiple threads sharing access to the same variables) is a gigantic subject, so I’m going to gloss over it here by saying that things can go wrong in deeply weird ways when threads accidentally overwrite each other’s updates to a variable or work from different copies of the shared variable. Synchronization can stop that from happening if you use it correctly, but at the cost of a hit to performance and the need to be very very careful that you don’t introduce deadlocks.

public synchronized void example() {
   //do things
}

Using the synchronization keyword on a method (like in the example above) synchronizes access to that entire method, which is generally pretty safe but unless you have a very small method you may be synchronizing a bigger chunk of code than you absolutely need to, which is more of a performance hit than necessary. Because synchronized blocks/methods can only be accessed by one thread at a time, they really slow down processing. The larger a chunk of code you synchronize, the worse the performance hit is.

class Example {
   Message m;

   public Example(Message m) {
       this.m = m;
   }

   public void doThings() {
       String name = Thread.currentThread().getName();
       synchronized(m) {
           //actually do things with m
       }
   }
} 

The synchronization method, while it makes it easier to synchronize only the part you need, also makes it easier to mess things up by introducing a deadlock. A deadlock happens if thread A needs locks on objects Y and Z and thread B needs locks on objects Z and Y in that order. If A locks Y and waits for Z to be unlocked, and B locks Z and waits for Y to be unlocked, both threads wait forever and nothing happens until you restart your program. If you lock on multiple objects (which you should definitely do if you need to update multiple shared objects in the same block of code), make sure that you absolutely always lock on those objects in the same order. The same problem applies to mysql deadlocks, which can really suck to debug if your codebase is large enough.

While we’re at it, according to stack overflow, synchronized(this) can be dangerous because it synchronizes on the entire instance. If you have another block that synchronizes on this, it can’t run until the other lock on this unlocks. It also means any external locks on that object can’t run until it’s unlocked, which can cause serious performance problems if you do it enough.

Aside from being very careful when you do use synchronized, the best advice I can give you is to use it as little as possible. If you can, just don’t have shared state. Particularly in web programming, you generally shouldn’t keep state around for longer than it takes to process a request.

Finally, if you use synchronized and mess it up, don’t waste time beating yourself up about it. Concurrency is even worse than timezones and everyone messes it up sometimes.