You have not told us what kind of multi-threading you do, but I presume you do multi-threading of the locking kind, (you use the
synchronized keyword of java,) otherwise you probably would not be asking the question.
As testing has gained ground in the software engineering discipline during the last decade or so, multi-threading of the locking kind has fallen from favor proportionally. That's because multi-threading code of the locking kind cannot really be tested.
Instead, the modern line of thinking with respect to multi-threading is to prefer mechanisms which eliminate dependencies between threads. This way, the code can be properly tested in a sequential fashion, and it is (sort of) guaranteed to work when put in parallel threads, because the parallel threads do not depend on each other.
One very important tool for accomplishing this is message passing. This means that each thread receives data to operate upon in a message queue, and sends processed data out via another message queue. The messages are immutable objects, so it is impossible for one thread to inadvertently modify the contents of a message, causing corruption in a different thread. Essentially, locking is still used, but it is localized in just one small class, the class that implements the actual message queue, which can be safely be presumed to already be thoroughly tested and in perfect working order.
If you have a lot of data to process, you might think that collecting it and packaging it into immutable objects and placing it in queues might represent a significant overhead, but actually this tends to be offset (sometimes greatly offset) by the fact that once a thread has a chunk of data to process, it can work on it without having to keep locking it all the time. You see, every time you try to lock something which happens to already be locked by another thread, your thread gets placed in a waiting state, which is a huge performance penalty.