It strikes me that there is a much simpler solution that doesn’t involve explicit semaphores and custom code (and bugs).
Just use a bounded BlockingQueue (javadoc) and have the threads use put(...) to add items to the queue.  When queue is full, put will block the thread that is calling it … until queue space is available.  If you don’t want the thread to block indefinitely, use offer with a suitable timeout.
solved Java 8 concurrent queue of semaphores