Difference between revisions of "Threads"

From Suhrid.net Wiki
Jump to navigationJump to search
Line 239: Line 239:
  
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
== Thread Communication ==
 +
 +
* Threads communicate with each other through their locking status on the objects.
 +
* The '''Object class''' has three methods wait(), notify() and notifyAll() which are used by Threads to communicate to each other.
 +
* wait(), notify() must be called from a synchronized context - the thread must own the object's lock before calling it.
 +
* wait() means the thread says - I am going to wait when this object's lock is release till another thread notify()'s me.
 +
* notify() means the thread says - I am done running the job after acquiring the object's lock, I will now release the lock, so that threads wait()'ing for this object can run.
 +
*
  
 
[[Category:OCPJP]]
 
[[Category:OCPJP]]

Revision as of 01:10, 12 June 2011

  • Think of Thread as the "worker" and Runnable as the job.
  • Define work to be done in a class that implements Runnable.
  • Instantiate the thread using the runnable object. (Thread is in the new state)
  • Then start() it. (Thread moves to the runnable state, eligible to run, perhaps waiting for the scheduler to run it)
class Job implements Runnable {
     public void run() {
         //work to be performed in  a separate thread.
     }
}

Job j = new Job();
Thread t = new Thread(j);
t.start();
  • When thread actually runs it is in the running state.
  • The thread can also go into waiting/blocked/sleeping state. e.g. waiting for an IO Resource such as a packet to arrive. In other words it is NOT runnable.
  • Once run() completes the Thread goes to the dead state. You cannot call start() again on it. Of course, the thread object itself can still be used.

SLEEP

  • Be careful of the Thread classes static methods such as sleep() and yield(). They refer to the current executing thread! Do not be misled when they are invoked using a thread object.
  • e.g. t1.sleep() will not cause Thread t1 to sleep(), it causes the current executing thread to sleep().
  • sleep() specifies that the Thread must go to sleep for at least the specified duration.
  • What does this mean, it means that when the sleep duration expires and thread wakes up, the thread immediately does not go to running - it goes to the runnable state.
  • so sleep() gives the minimum duration that the thread will not run. You cannot use it as an accurate timer!

JOIN

  • t.join() will take the current thread from which this code is called and join it to the end of t.
  • This means the thread from which join() is called will wait until t finishes before it runs again.


Synchronization

  • Control access to shared object by multiple threads.
  • Only methods and code blocks can be synchronized.
  • Synchronization happens through object locks.
  • Suppose a synchronized method is called, then the lock of the object that invoked the method is used for synchronization.
  • When a thread enters a synchronized method, since an object has only one lock - even other synchronized methods in the class cannot be entered for that object by other threads.
  • The following program invokes three threads to each print a single different letter 100 times.
  • If the code is not synchronized, different threads can print the same letter - because they would start printing before the letter has got a chance to change.
  • Note that printLetter() is not synchronized. Why ? Because printLetter is invoked using a LetterPrintJob object and each thread uses a separate LetterPrintJob object.
  • So you have to synchronize on the lock of the shared object which in this case is the StringBuffer.
class LetterPrintJob implements Runnable {

	private StringBuffer strBuf;

	LetterPrintJob(StringBuffer strBuf) {
		this.strBuf = strBuf;
	}

	@Override
	public void run() {
		printLetter();
	}

	void printLetter() {
		synchronized(strBuf) {
			char letter = strBuf.charAt(0);
			for(int i=0; i < 100; i++) {
				System.out.println(Thread.currentThread().getName() + ":" + letter);
			}
			strBuf.setCharAt(0, ++letter);
		}
	}
}

public class SynchroTest {

	public static void main(String[] args) {
		StringBuffer strBuf = new StringBuffer("A");
		new Thread(new LetterPrintJob(strBuf),"T1").start();
		new Thread(new LetterPrintJob(strBuf),"T2").start();
		new Thread(new LetterPrintJob(strBuf),"T3").start();
	}

}


  • Since each thread has its own stack, two threads executing the same method concurrently will use different copies of local variables.
  • Shared data needs to be synchronized. For non-static members - usually accessed by non-static methods, we need to prevent threads that use the same object instance to invoke the method. Usually by marking

such methods as synchronized.

  • Threads using different instances is not a problem - since the non-static data is not shared.
  • Similarly for static fields that need to be synchronized, mark the static accessor methods as synchronized.
  • However problems arise when non-static methods access static fields and static methods access non-static members through instances !
  • Careful coding is necessary to make the class thread-safe.
  • See example below on how threads can cause even a Thread-safe class like a Vector to fail.
  • Individual methods like add() and remove() are synchronized - which prevents multiple threads using these methods concurrently on a single vector instance.
  • However, this will not prevent situations where a different thread can manipulate the vector in between two invocations of the synchronized methods.
class VectorJob implements Runnable {

	Vector<String> v;

	VectorJob(Vector<String> v) {
		this.v = v;
	}

	public void run() {
			try {
				if(v.size() > 0) {
					Thread.sleep(10);
					v.remove(0);
				}
			} catch (Exception e) {
				System.out.println("Oops ");
				e.printStackTrace();
			}
        }

public class SynchroTest1 {

	public static void main(String[] args) {

		Vector<String> v = new Vector<String>();
		v.add("Troll");
		new Thread(new VectorJob(v), "T1").start();
		new Thread(new VectorJob(v), "T2").start();
		
	}
}
  • What happens here is T1 checks Vector size is non-zero before removing the element and then goes to sleep
  • T2 gets a chance to run, checks size is non-zero and goes to sleep
  • T1 wakes up - removes the first element
  • T2 wakes up - proceeds to remove the first element ( T2 was sure that the size > 0 before sleeping) and an exception occurs.
  • Solution is to externally synchronize the code block on the vector element.

Deadlock

  • Example-1 using synchronized blocks. Straightforward to see how threads can get deadlocked.
public class Deadlock {
	
	String resourceA = "resourceA";
	String resourceB = "resourceB";

	public static void main(String[] args) {
		final Deadlock dl = new Deadlock();
		
		new Thread("Reader") {
			public void run() {
				dl.read();
			}
		}.start();
		
		new Thread("Writer") {
			public void run() {
				dl.write("AA", "BB");
			}
		}.start();
	}
	
	String read() {
		synchronized(resourceA) {
			synchronized(resourceB) {
				return resourceA + resourceB;
			}
		}
	}
	
	void write(String a, String b) {
		synchronized(resourceB) {
			synchronized(resourceA) {
				resourceA = a;
				resourceB = b;
			}
		}
	}

}
  • Example 2 - Same example using synchronized methods(). Notice, it is more difficult to spot the deadlock condition here.
  • How can a deadlock occur here ? A possible scenario:
  • Thread-A gets the lock for the thing1 object by calling thing1.foo(thing2)
  • Before Thread-A can call the lock for thing2 object by calling thing2.bar(), Thread-B has acquired the lock for thing2 by calling thing2.foo(thing1)
  • Since Thread-A has to call thing2.bar(), it waits for Thing2's lock being held by Thread-B.
  • Since Thread-B has to call thing1.bar(), it waits for Thing1's lock being held by Thread-A.
  • Deadlock !
class Thing {
	
	private String str;
	
	public Thing(String str) {
		this.str = str;
	}

	synchronized void foo(Thing t) {
		t.bar();
	}

	synchronized void bar() {
	}
	
	public String toString() {
		return str;
	}
	
}

public class Deadlock1 {

	public static void main(String[] args) {
		
		final Thing thing1 = new Thing("Thing1");
		final Thing thing2 = new Thing("Thing2");
		
		new Thread("Thread-A") {
			public void run() {
				thing1.foo(thing2);
			}
		}.start();
		
		new Thread("Thread-B") {
			public void run() {
				thing2.foo(thing1);
			}
		}.start();
	}
}

Thread Communication

  • Threads communicate with each other through their locking status on the objects.
  • The Object class has three methods wait(), notify() and notifyAll() which are used by Threads to communicate to each other.
  • wait(), notify() must be called from a synchronized context - the thread must own the object's lock before calling it.
  • wait() means the thread says - I am going to wait when this object's lock is release till another thread notify()'s me.
  • notify() means the thread says - I am done running the job after acquiring the object's lock, I will now release the lock, so that threads wait()'ing for this object can run.