| Java 2 Basics > Threads | Index - Previous - Next |
A Thread is a lightweight approach for running different pieces of code at the same time without needing to call the operating system to start new processes.
Threads are commonly set up by subclassing the Thread class or by implementing the Runnable interface and then passing the object reference to a Thread object. In both cases, a run() method should be provided by the programmer. To make a Thread object eligible to run, you must invoke the start() method. Once the processor has time, the Java's thread scheduler will execute the code contained in run().
Please note that run() must be public as it cannot be overridden with weaker access level.
class Test {
public static void main(String args[]) {
MyThread t = new MyThread();
t.start();
for (int i = 0 ; i < 10 ; i++) {
Test.Wait(1);
System.out.print(".");
}
}
static void Wait(int s) {
s *= 1000;
long time = System.currentTimeMillis();
while (System.currentTimeMillis()-time < s) { }
}
}
class MyThread extends Thread {
public void run() {
Test.Wait(5);
System.out.println(" A threat is bothering!");
Test.Wait(2);
System.out.println(" Here I am again!");
}
}
Result:
.... A threat is bothering!
.. Here I am again!
....
class Test {
public static void main(String args[]) {
MyObject or = new MyObject();
Thread t = new Thread(or);
t.start();
for (int i = 0 ; i < 10 ; i++) {
Test.Wait(1);
System.out.print(".");
}
}
static void Wait(int s) {
s *= 1000;
long time = System.currentTimeMillis();
while (System.currentTimeMillis()-time < s) { }
}
}
class MyObject implements Runnable {
public void run() {
Test.Wait(5);
System.out.println(" A threat is bothering!");
Test.Wait(2);
System.out.println(" Here I am again!");
}
}
Result:
.... A threat is bothering!
.. Here I am again!
....
When the run() methods returns, the thread has finished its task and is considered dead. Once a thread is dead, it may not be started again. However, as a Thread is basically an Object, you still may call its other methods.
A thread may show many states:
A thread that is running is in execution of the code contained in the run() method. A loop or some "waiting routine" as shown in the previous examples don't prevent threads from being in the running state.
In this state, the thread is "ready" to execute. As soon as the Java's thread scheduler finds a chance, the thread in this state will be executed (or will continue execution). For example, when a Thread is started via start(), the Ready state is set. Once conditions allow the scheduler to execute the thread, run() will be invoked. This may not happen immediately, if the CPU is busy or there are some heavy Threads already running that don't free the CPU up.
A sleeping thread passes time without doing anything and without using the CPU. A sleeping Thread is PUT TO SLEEP via the sleep() method. A loop that is expecting some value to continue program flow doesn't put a thread in the sleeping state.
class Test {
public static void main(String args[]) {
Thread t = new MyThread();
t.start();
for (int i = 0 ; i < 50 ; i++) {
try {
Thread.sleep(50);
}
catch (InterrupedException e) { }
System.out.print(".");
}
}
}
class MyThread extends Thread {
public void run() {
try {
sleep(1000);
}
catch (InterruptedException e) { }
System.out.println(" I ran!");
}
}
Result:
................... I ran!
...............................
The method stop() is static and causes the thread from where it is called to be suspended for a given amount of time:
public static void sleep(long milliseconds) throws InterruptedException public static void sleep(long milliseconds, int nanoseconds) throws InterruptedException
When the thread stops sleeping, it moves to the Ready state. It is also possible to move a sleeping thread to the Ready status implicitly via the interrput() method. This action raises a "InterruptedException". Never forget that as sleep() is static, the current object upon it is invoked, is not affected.
class Test {
public static void main(String args[]) {
Thread t = new MyThread();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
t.interrupt();
}
}
class MyThread extends Thread {
public void run() {
System.out.println("I've started.");
try {
System.out.println("Now, I'll sleep");
sleep(8000);
System.out.println("I'll wake up now");
}
catch (InterruptedException e) {
System.out.println("You woke me up!");
}
System.out.println("All done!");
}
}
Result:
I've started.
Now, I'll sleep
You woke me up!
All done!
The message "I'll wake up now" is never shown as the sleeping thread is interrupted and an Exception is raised.
Methods that usually perform input or output have to wait for some occurrence in the outside world before they can proceed; this behaviour is known as blocking. Consider this example remembering that we have used the API we have studied so far:
class Test {
static boolean done = false;
public static void main(String args[]) {
Thread t = new MyThread();
t.start();
System.out.print("Type your name: ");
try {
Thread.sleep(5000);
while (!done) {
System.out.print("\nWhat are you waiting");
System.out.print(" for?\nChristmas?");
System.out.print("\nType your name: ");
Thread.sleep(5000);
}
} catch (InterruptedException e) {}
System.out.println("All Done!");
}
}
class MyThread extends Thread {
public void run() {
try {
int c = System.in.read();
}
catch (IOException e) {
System.out.println("I/O Trouble!");
}
System.out.println("Ok, wait up...");
Test.done = true;
}
}
Result: (I try to write my name but I seem to be too slow)
C:\temp>java Test
Type your name: Er
What are you waiting for?
Christmas?
Type your name: Erne
What are you waiting for?
Christmas?
Type your name: Ernest
Ok, wait up...
All Done!
When a thread exist a blocking method, it moves to the Ready state.
In this state, the thread is "ready" to execute. As soon as the Java's thread scheduler finds a chance, the thread in this state will be executed (or will continue execution). For example, when a Thread is started via start(), the Ready state is set. Once conditions allow the scheduler to execute the thread, run() will be invoked. This may not happen immediately, if the CPU is busy or there are some heavy Threads already running that don't free the CPU up.
The run() method returned. There's no way to step back from this state.
Every thread has a priority, an integer from 0 to 10; threads with higher priority get preference over threads with lower priority. There's no guarantee about how this priority is implemented. Sometimes, the highest priority thread may consume all the CPU time until it finishes, and then the CPU will execute the next available thread in order of priority. On some Java implementations and/or operating systems, the highest priority thread is executed "more often", and from time to time, other lower priority threads also get a chance to execute. Threads that take too long to execute or do very intensive CPU tasks, should "free" the CPU from time to time using the yield() method.
Thread t = new MyThread(); t.setPriority(9); t.setPriority(Thread.MAX_PRIORITY); t.setPriority(Thread.MIN_PRIORITY); t.setPriority(Thread.NORM_PRIORITY); int c = t.getPriority();
A call to the yield() method causes the currently executing thread to move to the Ready state if the scheduler is willing to run any other thread in place of the yielding thread.
for (int i = 0 ; i < 640 ; i++) {
for (int j = 0 ; j < 480 ; j++) {
// CalculatePixel(i,j);
yield();
}
}
Most schedulers do not stop the yielding thread from running in favour of a thread of lower priority, so a thread of MAX_PRIORITY may not give a chance of execution to a thread of NORM_PRIORITY just because of yield().
Every object has a lock. A lock is controlled by, at most, one single thread. The lock controls access to the object's synchronized code. A thread that wants to execute an object's synchronized code must first attempt to acquire that object's lock. If the lock is available -that is, if it is not already controlled by another thread- then all is well. If the lock is under another thread's control, then the attempting thread goes into the Seeking Lock state and only becomes ready when the lock becomes available. When a thread that owns the lock passes out of the synchronized code, the thread automatically gives up the lock.
Synchronization is useful when you have a single object that is used by more than one thread. When you apply the synchronized keyword to a block of code (usually a method) you will be sure that once a thread starts to execute that synchronized block, the object containing that block will remind blocked for all other threads until the synchronized block is exited or some other condition specified by the programmer arises.
class CarDealer {
int available_cars = 1;
synchronized boolean buyCar(int mIncome, String car) {
boolean sell = false;
if (available_cars > 0) {
available_cars--;
if (mIncome > 1500) sell = true;
// Consult banks, validate credit and so on...
// ...
if (car.equals("jaguar") && mIncome < 5000)
sell = false;
}
if (!sell) available_cars++;
return sell;
}
}
A buyCar() method sells cars to customers based on their monthly income and the kind of car they want. The method just confirms if it was possible or not to sell the card returning a boolean value. It also keeps track of the available cars there are in stock.
Suppose that one customer thread wants to buy a jaguar:
car_dealer_refernce.buyCar(2000,"jaguar");
Our polite BuyCar() method first reserves a car (subtracting a car from the stock) and then verifies the monthly income and checks this information with banks, etc. Let's just imagine that the operation takes a while. So what about if a second customer thread wants to buy a car?. At that moment, there wouldn't be more available cars and a false answer will be returned. The first buyer didn't have enough money to buy a jaguar so a false answer was given to him too. So the BuyCar() method couldn't sell its last car to anyone. BuyCar() had to tell the second customer that there was a first customer interested in a car, but that he could wait to see if the sale was completed or not.
The synchronized keyword helps us to step out of this problem. When the first customer asks for a car, he locks the object. When the second customer asks for a car, he doesn't receive an answer right away, instead, he waits until the first customer finishes his transaction. This is the same as waiting for a String to come out of a Socket stream. The call just stays there, waiting, until there is an answer.
When a thread reaches a synchronized block of a class:
class Test {
static boolean done = false;
public static void main(String args[]) {
CarDealer cd = new CarDealer();
OtherCustomer oc = new OtherCustomer(cd);
Thread t = new Thread(oc);
t.start();
try {
// Thinking, should I sell or not?
Thread.sleep(1*1000);
} catch (InterruptedException e) { }
System.out.print("I am looking ");
System.out.print("for a car...\n");
if (cd.buyCar(6000,"ford")) {
System.out.println("Me: I've got a new car");
} else {
System.out.println("Me: I'll keep walking");
}
}
}
class OtherCustomer implements Runnable {
CarDealer cd;
OtherCustomer(CarDealer cd) {
this.cd = cd;
}
public void run () {
System.out.print("Other customer is looking ");
System.out.print("for a car...\n");
if (cd.buyCar(500,"jaguar")) {
System.out.println("OC: I've got a new car");
} else {
System.out.println("OC: I'll keep walking");
}
}
}
class CarDealer {
int available_cars = 1;
synchronized boolean buyCar(int mIncome, String car) {
boolean sell = false;
if (available_cars > 0) {
available_cars--;
if (mIncome > 1500) sell = true;
if (car.equals("jaguar") && mIncome < 5000)
sell = false;
try {
// Thinking, should I sell or not?
Thread.sleep(5*1000);
} catch (InterruptedException e) { }
}
if (!sell) available_cars++;
return sell;
}
}
Result as is:
Other customer is looking for a car...
I am looking for a car...
OC: I'll keep walking
Me: I've got a new car
Result without the synchronized keyword:
Other customer is looking for a car...
I am looking for a car...
Me: I'll keep walking
OC: I'll keep walking
Classes also have locks, they apply to static features of a class and not to instances of a class.
Sometimes you want to have more control of the synchronized feature. For example, you may only block until a condition is met. These two keywords should only be used under synchronized blocks of code.
When wait() appears, the calling thread enters into a monitoring state, and unblocks the object without needing to exit the complete synchronized block. This state can last a fixed amount of time and not until notify() is called by using: wait(long milliseconds). wait() may throw a "InterruptedException" that you must catch.
class CarDealer {
int available_cars = 1;
synchronized boolean buyCar(int mIncome, String car) {
boolean sell = false;
if (available_cars > 0) {
available_cars--;
if (mIncome > 1500) sell = true;
if (car.equals("jaguar") && mIncome < 5000)
sell = false;
try {
wait(5*1000);
} catch (InterruptedException e) {}
}
if (!sell) available_cars++;
return sell;
}
}
Result:
Other customer is looking for a car...
I am looking for a car...
Me: I'll keep walking
OC: I'll keep walking
This is a modified version of the last example. We have only changed the sleep call by wait(). wait(), unlike sleep(), releases the lock of the object so the BuyCar() method doesn't block for the other thread.
One arbitrary chosen thread gets moved out of the monitor's waiting pool and into the Seeking Lock state. To cause the same effect on all running threads, you can use notifyAll().
class Test {
public static void main(String args[]) {
BeerDealer bd = new BeerDealer();
Timer t = new Timer();
t.schedule(new TTask(bd), 2000);
if (bd.buyBeer()) {
System.out.println("I'm allowed to drink");
} else {
System.out.println("I'm not allowed to drink");
}
System.out.println("Execution ended");
System.exit(0);
}
}
class TTask extends TimerTask {
BeerDealer bd;
TTask(BeerDealer bd) {
this.bd = bd;
}
public void run() {
bd.setAge(18);
}
}
class BeerDealer {
public static int age = 17;
synchronized boolean buyBeer() {
while (age < 18) {
try {
wait();
} catch (InterruptedException e) {}
}
return true;
}
synchronized void setAge(int age) {
this.age = age;
notify();
}
}
Result:
I'm allowed to drink
Execution ended
Finally, wait() and notify() are usually used together to avoid data corruption under some circumstances. In this example, we simulate an answer machine.
class Test {
static boolean done = false;
public static void main(String args[]) {
Mailbox answerMachine = new Mailbox();
ThreadOBJ to = new ThreadOBJ(answerMachine);
Thread t = new Thread(to);
t.start();
System.out.println("Welcome!");
System.out.println("What kind of message do you");
System.out.println("want to leave?\n");
System.out.println("1 - Hi, call me back please!");
System.out.println("2 - Please return my calls!");
System.out.println("3 - Exit\n");
System.out.print("Select (1-3) and press Enter: ");
int c;
try {
exit: while ((c = System.in.read()) != -1) {
switch(Character.getNumericValue((char)c)) {
case 1:
answerMachine.storeMessage
("Hi, call me back please!");
break;
case 2:
answerMachine.storeMessage
("Please return my calls!");
break;
case 3:
break exit;
}
}
} catch (IOException e) {}
answerMachine.hungup();
System.out.println("Good bye!");
}
}
class ThreadOBJ implements Runnable {
Mailbox mb;
public ThreadOBJ (Mailbox mb) {
this.mb = mb;
}
public void run() {
String s;
while (!((s = mb.retriveMessage()).equals("<>"))){
System.out.println(s);
}
System.out.println("Thread canceled");
}
}
class Mailbox {
private boolean request;
private String message;
public synchronized void storeMessage(String m) {
while (request) {
try {
wait();
} catch (InterruptedException e) { }
System.out.println("Waiting input");
}
request = true;
message = m;
notify();
}
public synchronized String retriveMessage() {
while (!request) {
try {
wait();
} catch (InterruptedException e) { }
}
request = false;
notify();
return message;
}
public void hungup () {
request = false;
storeMessage("<>");
}
}
Result:
C:\temp>java Test
Welcome!
What kind of message do you
want to leave?
1 - Hi, call me back please!
2 - Please return my calls!
3 - Exit
Select (1-3) and press Enter: 1
Hi, call me back please!
2
Please return my calls!
3
Good bye!
Thread canceled
| © Ernesto Garbarino | Top |