I wonder whether you could help me with a couple of threads treading on one another's feet.
Following is the class in question, which represents a sort of simple semaphore on object locking. The only two states allowed should be 0 and 1, respectively available and not available. (just for study purposes) immagine then Thread1 and Thread2 calling both acquire() and release() in their run() methods.
<code> public class Atomic { private volatile int semaphore = 0; public void acquire() { semaphore = semaphore + 1; } public void release() { semaphore = semaphore - 1; } } </code>
and this is the javap -c Atomic result:
<code> Compiled from Atomic.java public class Atomic extends java.lang.Object { public Atomic(); public void acquire(); public void release(); }
When two threads interact on this particular object, the possible values for semaphore held in an unstable state are 2 and -1. If it's quite simple to account for 2, it's not so for -1. I would like to understand why. Furthermore, I have just approched opcodes, so don't take anything for granted, please.
One more question: are incrementing and decrementing atomic operations, is then every single opcode an atomic operation?
It may be best to include the full example (from Eckel's Thinking in Java):
//: c13:Invariant.java public interface Invariant { InvariantState invariant(); }
//: c13:InvariantOK.java // Indicates that the invariant test succeeded public class InvariantOK implements InvariantState {}
//: c13:InvariantFailure.java // Indicates that the invariant test failed
public class InvariantFailure implements InvariantState { public Object value; public InvariantFailure(Object value) { this.value = value; } }
//: c13:Semaphore.java // A simple threading flag
public class Semaphore implements Invariant { private volatile int semaphore = 0; public boolean available() { return semaphore == 0; } public void acquire() { ++semaphore; } public void release() { --semaphore; } public InvariantState invariant() { int val = semaphore; if(val == 0 || val == 1) return new InvariantOK(); else return new InvariantFailure(new Integer(val)); } }
//: c13:SemaphoreTester.java // Colliding over shared resources
public class SemaphoreTester extends Thread { private volatile Semaphore semaphore; public SemaphoreTester(Semaphore semaphore) { this.semaphore = semaphore; setDaemon(true); start(); } public void run() { while(true) if(semaphore.available()) { yield(); // Makes it fail faster semaphore.acquire(); yield(); semaphore.release(); yield(); } } public static void main(String[] args) throws Exception { Semaphore sem = new Semaphore(); new SemaphoreTester(sem); new SemaphoreTester(sem); new InvariantWatcher(sem).join(); } }
When I run the programme on my Pentium 133 with Windows 95, the InvrianteWatcher detects the semaphore's value in an invalid state (2). If you remove all the yields() withing the run() method the InvariantWatcher detect -1. What I'd like to know is the sequence of opcodes (the mixing of both threads) that would set semaphore's value to 2 and -1.
I would be grateful if you could be as thorough as possible in your explanaition. I am only trying to get a deeper understanding of the underlaying logic of threads.