CIS 4350
Rolf Lakaemper
Definitions first (Wikipedia):
“In computer science, a thread of execution is the smallest sequence of programmed instructions that can be managed independently by an operating system scheduler… Multiple threads can exist within the same process and share resources such as memory”
Let’s start with a simple example.
We want to write a program, that prints out a text every second.
public static void main(String[] args) { long time = System.currentTimeMillis(); int counter = 0; while (true) { long currentTime = System.currentTimeMillis(); if (currentTime - time > 1000) { time = currentTime;
System.out.println("Hello “+ counter)); counter++;
}
}
}
public static void main(String[] args) throws InterruptedException
{ long time = System.currentTimeMillis(); int counter = 0; This is a pause command. It indirectly points us to Threads, but let’s not care right now.
while (true) {
Thread.sleep(1000);
System.out.println("Hello " + counter); counter++;
}
}
The limitation is, that the program
(a) pauses for 1 second
(b) requires a loop to be performed!
Let’s extend the task, so these limitations become clear:
We want the user to be able to end the program, by clicking ok in a dialog box. Unfortunately, when creating a dialog box, it waits for input (if we choose JOptionPane.showMessageDialog)).
This does not work!
Wait a second
Print Time
Get input
End
It’s stuck right here!
What we need is a user input that does not wait.
JAVA of course provides these, but, for this example, let’s build our own version.
Our non-waiting box will explain why Threads are needed: our example problem is trying to handle two tasks: printing the time and waiting for input.
This problem can be modeled in a more elegant way than in a single loop:
We have one process (our program), with two sub-processes (THREADS), that run independently, yet communicate via a field (break flag)
Process
Thread 1 Thread 2
Wait a second
Print Time
Check break-flag
End
Break flag
Get User Input
Set break flag
“…a thread of execution is the smallest sequence of programmed instructions that can be managed independently by an operating system scheduler…
Our two threads! The time-print-thread, and the input-thread.
Multiple threads can exist within the same process and share resources such as memory”
Our process: one program. The shared resource: the break flag.
When you start your usual JAVA program, it runs in a single thread, which starts the method main().
Additional Threads can be started by instantiating classes of Type “Runnable”, i.e. classes that implement the “Runnable” interface.
“Runnable” declares the method “run()”
There’s a default implementation, the “Thread” class, we can extend our own classes from.
Process
Thread 1 Thread 2
Break flag
public class Main { private boolean breakFlag; class TimePrint extends Thread {
} class UserInput extends Thread {
}
// ----------------------------
Main() { breakFlag = false; new TimePrint().start(); new UserInput().start();
}
} public static void main(String[] args) throws InterruptedException { new Main();
}
We are starting 3 threads here!
1.
“main”
2.
3.
Thread1
Thread2
“main” just dies after creating the other two.
Thread1 dies when the break-condition is true
Thread2 dies when ok was clicked.
The process terminates when all threads are dead,
NOT when main terminates!
class TimePrint extends Thread { public void run(){ int counter = 0; while (!breakFlag){ try {
Thread.sleep(1000);
}
} catch (InterruptedException ex) {
System.out.println("Hello "+counter); counter++;
}
}
}
Shared resource!
class UserInput extends Thread { public void run(){
JOptionPane.showMessageDialog(null, "Stop the
Program"); breakFlag = true;
}
}
Shared resource!
class UserInput implements Runnable { public void run(){
JOptionPane.showMessageDialog(null, "Stop the Program"); breakFlag = true;
}
}
… this version needs to be started using: new Thread(new UserInput()).start();
Threads can have multiple “states”
We might talk about that later.
…but we don’t necessarily have to.
We share resources!
If one thread writes to a resource while another one reads from it, we might be in trouble!
(the problem is that we only MIGHT be in trouble. One time we are, another we are not, which makes debugging ugly!)
The trouble: if the resources consist of multiple parts (e.g. an array, consisting of many members). If thread1 re-writes a part while thread2 reads another part, thread2 might have different partial versions of the resource read after completion. Even worse: if thread1 deletes the resource, while thread2 reads it, the program might crash!
If resources consist of only a single part, everything is fine.
time
Thread1
Starts to read
Thread2 writes
Thread1
Continues to read
Thread1
Reading result:
Array Array Array Array
time
Thread1
Starts and finishes reading
Thread2 writes
Thread1
Reading result:
A resource is thread safe when its access is
“atomic”, i.e. a single virtual access cycle is needed.
This can be achieved by “locks”, which regulate access order.
Example:
Version 1: no locking. Concurrency problem
Method1
Access to
Array
Method2
Access to
Array
Trouble!!!
Array
Version 1: locking. No concurrency problem
Method1
Access to
Array
Lock
Access
Unlock
Method2
Access to
Array
Array
A lock is an atomic resource
It has two methods, which are virtual one cycle methods: lock() and unlock()
If a locked resource is attempted to be locked again, it blocks the access (makes the Thread wait) until it gets unlocked.
“Locking” a data structure is to surround the multicycle access to it with a lock.
(example, see course website: “ThreadExamples”)