I'm trying to get the AABBAABB sequence to infinity but what I get is AABBAAABB. The java code is the following:
private static Semaphore semA = new Semaphore(2);
private static Semaphore semB = new Semaphore(0);
public static void main(String[] args) {
while(true) {
new P1A().start();
new P2B().start();
try {
Thread.sleep(1000);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
static class P1A extends Thread{
public void run() {
try {
semA.acquire();
System.out.print("A");
if(semA.availablePermits() == 0) {
semB.release(2);
}
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
static class P2B extends Thread{
public void run() {
try {
semB.acquire();
System.out.print("B");
if(semB.availablePermits() == 0)
semA.release(2);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
According to my code, once printed A twice it should suspend Thread P1A for Thread P2B to run two more times. But I can't get it despite the traffic lights release 2 seats ...
Can you help me?
CodePudding user response:
Here's what is happening in your code:
- Loop starts. Threads: A1, B1 are created.
- A1 prints "A". B1 waits for a permit.
- Loop starts again after sleeping. Threads: A2, B2 are created.
- A2 prints "A" - number of available permits in the semaphore A is 0, so 2 permits are released to the B semaphore. B2 waits for a permit (yet).
- B1 and B2 both acquire a permit and print "B". Since both threads acquired the permit, number of available permits is 0 - when both threads check that condition, both release 2 permits in the semaphore A. Semaphore A has 4 permits at this point, which cause the printed sequence to fall apart:
AABBAAAABB...
.
After adding thread identifiers to the print statements and adding additional logging after releasing permits the whole run looks like this (println
used instead of print
for better readability):
A (Thread-A1)
A (Thread-A2)
B (Thread-B1)
B (Thread-B2)
Thread-A2 released B
Thread-B1 released A
Thread-B2 released A
A (Thread-A3)
A (Thread-A4)
A (Thread-A5)
A (Thread-A6)
Thread-A6 released B
B (Thread-B3)
Thread-B3 released A
B (Thread-B4)
Thread-B4 released A
There may of course multiple ways of resolving that, one of which (I'd assume not the best, but working) could be a synchronized verification of the target state (Semaphore references have to be declared as final
):
if (semA.availablePermits() == 0) {
synchronized (semB) {
if (semB.availablePermits() == 0) {
semB.release(2);
}
}
}
Thanks to the synchronization, each thread that reaches the inner if
has access to the actual ("target") state - no parallel modification is possible in this case.
The behavior above is a compare-and-swap (CAS) operation - we verify if current value is a value we expect and if so, we act on it; otherwise we skip execution of the if
body. Semaphore actually partially uses this mechanism underneath (Semaphore.Sync
inner class), but it retries the incrementation until successful (or throws after overflowing):
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
I've created a GitHub repository where you can find the code for the logging result above (InitialWithLogging), a fixed code with comments (InitialFixedWithComments) and a refactored code that strictly represents the abstraction without duplication (SimplifiedSolution).