Sunday, June 19, 2011

Handling Semaphores with care

Exception is raised
As with most methods of locking or synchronization, there are some potential issues.

The number one thing to remember is, always release what you acquire. This is done by using try..finally constructs. Example:

// Bad -- semaphore not released if the exception is thrown
try {
sem.acquire();
sharedResource.doStuff();
sem.release();
} catch (IOException e) {
logger.warn("The resource is broken", e);
}

So instead use try…finally:

// Good
try {
sem.acquire();
sharedResource.doStuff();
} catch (IOException e) {
logger.warn("The resource is broken", e);
} finally {
sem.release();
}


Issue of lock ordering
The following class shows a deadlock that only the luckiest of you will avoid. You’ll notice that the two threads which acquire the two semaphore permits do so in opposite order. (try..finally is left out for the sake of brevity).

public static void main(String[] args) throws Exception {
Semaphore s1 = new Semaphore(1);
Semaphore s2 = new Semaphore(1);

Thread t = new Thread(new DoubleResourceGrabber(s1, s2));
// now reverse them ... here comes trouble!
Thread t2 = new Thread(new DoubleResourceGrabber(s2, s1));

t.start();
t2.start();

t.join();
t2.join();
System.out.println("We got lucky!");
}

private static class DoubleResourceGrabber implements Runnable {
private Semaphore first;
private Semaphore second;

public DoubleResourceGrabber(Semaphore s1, Semaphore s2) {
first = s1;
second = s2;
}

public void run() {
try {
Thread t = Thread.currentThread();

first.acquire();
System.out.println(t + " acquired " + first);

Thread.sleep(200); // demonstrate deadlock

second.acquire();
System.out.println(t + " acquired " + second);

second.release();
System.out.println(t + " released " + second);

first.release();
System.out.println(t + " released " + first);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}


If you run this, you will more than likely have a hung process. Issues of lock ordering apply to semaphores as much as regular mutexes or synchronization in Java. In some cases, timeouts (see note on tryAcquire() later in the article) can be used to prevent deadlocks from causing a process to hang up, but typically a deadlock is a symptom of a logic error which can be avoided. If you’re unfamiliar with deadlocks, I recommend you read up on them. Wikipedia has a decent article on deadlocks which applies to all languages equally.

The main things that you should be careful of when using semaphores (including binary semaphores, i.e. mutexes) are:



  • Not releasing after acquire (either missing release call or an exception is thrown and there is no finally block)
  • Long held semaphores, causing thread starvation
  • Deadlocks (as seen above)

No comments:

Post a Comment

Chitika