Java の Semaphore は acquire しなくても release できる
タイトルの通り。Javadoc にも書いてある。
https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/Semaphore.html#release()
There is no requirement that a thread that releases a permit must have acquired that permit by calling acquire(). Correct usage of a semaphore is established by programming convention in the application.
acquire していたかどうかに関係なく、release すれば新たに acquire 可能な permit が増える。どこまでいけるかというと、内部的に int で状態を管理しているので、Integer.MAX_VALUE まで。それ以上に release しようとすると、java.lang.Error が発生する。
そもそも普通はこの仕様を意図的に利用することは無いと思う (しやめてほしい) けど、プログラムのバグでうっかり acquire に対して release しすぎてしまうことはある。そのときに、過剰に release した permit が Integer.MAX_VALUE に到達するまでは動いてしまうので困りもの。アプリ側でちゃんとやってねと言われても……と思いつつ、まあそういうことなので、そのあたりは自分たちでラップして仕組み化したほうがいいかもしれない。