Conclusion
Excessive use of the synchronized keyword may be counterproductive as code scales up. The java.util.concurrent package offers a range of lightweight and flexible tools for most of your needs, which should help you to avoid reinventing the wheel. In this article, I've covered a range of tools available in the package.
The Semaphore class provides easily understood semantics and powerful capabilities in the form of methods such as acquire(), release(), etc. In addition, you can allow a semaphore to have a specific number of slots for callers to use, after which further callers are blocked.
Concurrent locks such as ReentrantLock() are even more flexible, allowing for a timed retry mechanism and the ability to check the number of outstanding attempts to lock the resource.
Data sharing between threads has traditionally been fraught with danger. However, the java.util.concurrent package provides a selection of thread-safe collections. This approach has the merit of reusing the well-known semantics of Iterators but in a manner that lends itself to safer multithreading. For example, if your strategy involves many reader threads and few writer threads, the ConcurrentCopyOnWrite class may be just what you need.
Also addressed is the area of thread-safe queues. You can use a very intuitive interface with the ConcurrentLinkedQueue class. This class provides the usual queue mechanisms of adding nodes, removing nodes, getting the queue size, and so on. By implementing the Executor interface, you can provide for very flexible inter-thread semantics. This mechanism is worthy of study.
I think all but the most complex of concurrency requirements can be met with the java.util.concurrent package. Even if you don't need to use it today, it's worthy of study for the time when you will need it.