Semaphore skipping additional threads
refresh() method must be processed by only one thread at a time ...


private final Semaphore refreshLock = new Semaphore(1);


A mutex is arguably a better synchronization object choice when you only need one thread at a time. So I would advise you to use a ReentrantLock instead, for instance.


synchronized (refreshLock) {
    if (refreshLock.hasQueuedThreads()) {
        System.out.println("skip this thread ... " + Thread.currentThread().getId());
        return; // don't put one more to the queue
    }
}



So in fact you are using not one but two synchronization objects, a semaphore and a monitor. The reason why you are doing this is because of your second requirement. However you can implement both of your requirements using only one synchronization object which would make your code simpler (hopefully).


Thread.sleep(5000);



No! I know you are doing this to emulate a long running operation. However long running operations should never execute within concurrent control, specially if it's running one at a time.

What this means effectively is that all threads calling refresh will be blocked by at least a whopping 5 seconds. 

So obviously you have to implement your refresh method as a process that runs on the background. Meaning that you will be returning a Future that may complete at any given moment. For a thread that will do the process this moment will be when the process has finished, for all others this will be almost immediately.

This is the resulting implementation:

private final ReentrantLock refreshLock = new ReentrantLock();
private boolean hasRefreshed = false;
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

public Future refresh() {
    System.out.println("call refresh()");
    try {
        refreshLock.lock();
        if(hasRefreshed) {
            System.out.println("already refreshed");
            return executor.submit(() -&gt; {});
        }

        hasRefreshed = true;
        return executor.submit(() -&gt; {
            System.out.println("process ... " + Thread.currentThread().getId());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("### DONE");
        });

    }finally {
        refreshLock.unlock();
    }
}


Ideally the synchronization logic would be implemented in a generic class that would be able to handle any use case.

You will also have to change your main a bit, so your program doesn't exit immediately.

IntStream.range(0, 12)
    .mapToObj(i -&gt; service.submit(new Task(i, scratch)))
    .forEach(f -&gt; {
        try {
            f.get();
        } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (ExecutionException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
    });