Simple counting fair semaphore implementation
As for your question of what happens to the intrinsic lock when the InterruptedException is thrown: As soon as wait() is called, the thread relinquishes the lock, which is stated in the documentation of Object.wait(). Continuing to hold the lock would defeat the purpose of wait(), which is to wait for some other thread to do something for which it needs the lock and be notified when that thread is finished with the task and no longer needs the lock, so that the current thread can start trying to re-acquire the lock.

This means that, in order to return from wait() and, if needed, execute the catch block, the thread has to acquire the lock again first. Once the thread does have the lock, however, entering the catch block will have no effect on the thread's ownership of the monitor.

I found this question, which, along with the answer given there, might contain what you're interested in.

Update

Here's a test to demonstrate that, even if the catch block is not synchronized, it will still only be executed after the thread has re-acquired the lock, because the thread must first return from wait(), which will only happen when it has the lock it was waiting on.

static void testControlFlow() {
    final Object lock = new Object();

    final Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    while (!Thread.currentThread().isInterrupted()) {
                        lock.wait();
                        /* Theoretically, it is possible that threadA will
                           be spuriously woken up AND be interrupted by
                           threadB between this wake-up and the next loop
                           iteration. If this unlikely scenario happens,
                           then the catch block will never be executed because
                           no InterruptedException will ever be thrown */
                    }
                }
            } catch (InterruptedException e) {
                System.out.println("threadA has caught an InterruptedException");
            }
        }
    });

    Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            while (threadA.getState() != Thread.State.WAITING) {
                //ensure that threadA acquires the lock before threadB
            }
            synchronized (lock) {
                threadA.interrupt();
                while (threadA.getState() == Thread.State.WAITING) {
                    //wait for threadA to wake up
                }
                System.out.println("threadA has woken up");

                while (threadA.getState() == Thread.State.RUNNABLE) {
                    //see if threadA can catch the exception without holding the lock
                }

                if (threadA.getState() == Thread.State.BLOCKED) {
                    System.out.println("threadA is now blocked and trying to acquire the lock");
                } else {
                    assert threadA.getState() == Thread.State.TERMINATED;
                    System.out.println("threadA has terminated while threadB was still holding the lock");
                }
            }
        }
    });

    threadA.start();
    threadB.start();
}


Output is:

threadA has woken up
threadA is now blocked and trying to acquire the lock
threadA has caught an InterruptedException