SLIDE 1
COMP 213
Advanced Object-oriented Programming
Lecture 21
Concurrency
SLIDE 2 What is Concurrency?
Concurrency is when two or more programs execute ‘at the same time’, either: sharing the same processor (multi-threading), or
- n different processors; these may be
in the same machine (multi-processor architecture), or in different machines (distributed computation).
SLIDE 3 What Use is Concurrency?
Reasons for using concurrency include: to perform a computation more quickly (usually using many processors), or to allow users to interact with programs while a computation is in progress. (E.g., allow more than one client to access a server at a time;
- r, allow users to interact with a GUI
while some computation proceeds)
SLIDE 4
Concurrency Comes at a Cost
For both kinds of concurrency, there are overheads: communication between different processors takes time (and the more processors, the higher the costs) to share one processor among many programs, one program has to be chosen to run; when its time is up, it has to be stopped, its current state stored, another program chosen to run, and its previous state restored — all of which takes time.
SLIDE 5
Multithreading
We will concentrate on multithreading: where several programs run on one processor. This is familiar from window managers that typically allow computer users to have several windows on the screen at once, each running a different program (and usually with several other programs running in the background).
SLIDE 6 Multithreading
Multithreading is typically implemented by time-slicing:
- ne program is chosen to run for a certain amount of time
(a ‘quantum’) — the period is not determined by the program;
- nce this amount of time has passed, the program is
stopped and the values of its variables are stored in memory; another program is chosen to run, and the values of its variables are restored from memory; the program runs, and this procedure is repeated for as long as there are programs left to run.
SLIDE 7 Multithreading
Multithreading is typically implemented by time-slicing:
- ne program is chosen to run for a certain amount of time
(a ‘quantum’) — the period is not determined by the program;
- nce this amount of time has passed, the program is
stopped and the values of its variables are stored in memory; another program is chosen to run, and the values of its variables are restored from memory; the program runs, and this procedure is repeated for as long as there are programs left to run.
SLIDE 8 Multithreading
Multithreading is typically implemented by time-slicing:
- ne program is chosen to run for a certain amount of time
(a ‘quantum’) — the period is not determined by the program;
- nce this amount of time has passed, the program is
stopped and the values of its variables are stored in memory; another program is chosen to run, and the values of its variables are restored from memory; the program runs, and this procedure is repeated for as long as there are programs left to run.
SLIDE 9 Multithreading
Multithreading is typically implemented by time-slicing:
- ne program is chosen to run for a certain amount of time
(a ‘quantum’) — the period is not determined by the program;
- nce this amount of time has passed, the program is
stopped and the values of its variables are stored in memory; another program is chosen to run, and the values of its variables are restored from memory; the program runs, and this procedure is repeated for as long as there are programs left to run.
SLIDE 10 Multithreading
Multithreading is typically implemented by time-slicing:
- ne program is chosen to run for a certain amount of time
(a ‘quantum’) — the period is not determined by the program;
- nce this amount of time has passed, the program is
stopped and the values of its variables are stored in memory; another program is chosen to run, and the values of its variables are restored from memory; the program runs, and this procedure is repeated for as long as there are programs left to run.
SLIDE 11 Multithreading
Note that this ‘switching’ between programs is not determined by the programs that are running, but by the ‘time slicer’: this might be, e.g., the window manager, the operating system,
- r the implementation of the Java interpreter.
Since we are interested in multithreaded Java programs, we’ll be concerned with time slicing as it is implemented by the Java interpreter.
SLIDE 12 Multithreading
Note that this ‘switching’ between programs is not determined by the programs that are running, but by the ‘time slicer’: this might be, e.g., the window manager, the operating system,
- r the implementation of the Java interpreter.
Since we are interested in multithreaded Java programs, we’ll be concerned with time slicing as it is implemented by the Java interpreter.
SLIDE 13
How to Write Concurrent Programs?
At its simplest, concurrency can be achieved by writing two or more programs, and then starting them. Java supports this simplest form of concurrency. A Java programmer can simply write two or more programs, and then start them. Of course, since we’re talking about Java, these programs have to be contained in some class. . . . . . we’ll look at the classes (and interfaces) needed for this, and the support that Java provides for managing the complexities that arise from running several programs at the same time.
SLIDE 14
How to Write Concurrent Programs?
At its simplest, concurrency can be achieved by writing two or more programs, and then starting them. Java supports this simplest form of concurrency. A Java programmer can simply write two or more programs, and then start them. Of course, since we’re talking about Java, these programs have to be contained in some class. . . . . . we’ll look at the classes (and interfaces) needed for this, and the support that Java provides for managing the complexities that arise from running several programs at the same time.
SLIDE 15
How to Write Concurrent Programs?
At its simplest, concurrency can be achieved by writing two or more programs, and then starting them. Java supports this simplest form of concurrency. A Java programmer can simply write two or more programs, and then start them. Of course, since we’re talking about Java, these programs have to be contained in some class. . . . . . we’ll look at the classes (and interfaces) needed for this, and the support that Java provides for managing the complexities that arise from running several programs at the same time.
SLIDE 16
Concurrency in Java
Concurrency in Java is programmed using threads. The notion of a thread of computation is captured in Java by the Thread class. In Java, all programs run in an instance of the Thread class. (main() methods run in a special thread called the ‘main’ thread. Remember the error messages: terminal output Exception in thread "main" ... )
SLIDE 17
Concurrency in Java
Concurrency in Java is programmed using threads. The notion of a thread of computation is captured in Java by the Thread class. In Java, all programs run in an instance of the Thread class. (main() methods run in a special thread called the ‘main’ thread. Remember the error messages: terminal output Exception in thread "main" ... )
SLIDE 18
How to Write Concurrent Programs?
— You have to write the programs. A program is just a sequence of instructions (assignments, conditionals, loops, method-calls, etc.) . . . which is exactly what you can place in a void method in java. So writing a program is the same as writing a void method.
SLIDE 19
How to Write Concurrent Programs?
— You have to write the programs. A program is just a sequence of instructions (assignments, conditionals, loops, method-calls, etc.) . . . which is exactly what you can place in a void method in java. So writing a program is the same as writing a void method.
SLIDE 20
How to Write Concurrent Programs?
— You have to write the programs. A program is just a sequence of instructions (assignments, conditionals, loops, method-calls, etc.) . . . which is exactly what you can place in a void method in java. So writing a program is the same as writing a void method.
SLIDE 21
How to Write Concurrent Programs?
— You have to write the programs. A program is just a sequence of instructions (assignments, conditionals, loops, method-calls, etc.) . . . which is exactly what you can place in a void method in java. So writing a program is the same as writing a void method.
SLIDE 22
How to Write Concurrent Programs?
— You have to write the programs. A program is just a sequence of instructions (assignments, conditionals, loops, method-calls, etc.) . . . which is exactly what you can place in a void method in java. So writing a program is the same as writing a void method.
SLIDE 23
Interface java.lang.Runnable
An interface is a good way of specifying, in Java, a void method. This is exactly what is done by: java.lang.Runnable public interface Runnable { public void run(); }
SLIDE 24
Multithreading
So you can write a program that you want to run concurrently with other programs by implementing interface Runnable. There is more to multithreading than just writing a program; the Java interpreter has to maintain a list of programs, and run them concurrently using time-slicing. The programmer gets the interpreter to do this with their program by: implementing interface Runnable with the program they want to run; creating a Thread (thread of computation) with that program; starting that Thread. You already know how to implement interfaces.
SLIDE 25
Multithreading
So you can write a program that you want to run concurrently with other programs by implementing interface Runnable. There is more to multithreading than just writing a program; the Java interpreter has to maintain a list of programs, and run them concurrently using time-slicing. The programmer gets the interpreter to do this with their program by: implementing interface Runnable with the program they want to run; creating a Thread (thread of computation) with that program; starting that Thread. You already know how to implement interfaces.
SLIDE 26
Creating a Thread
Class Thread has a constructor that takes a Runnable as a parameter. in class Thread public Thread(Runnable r) { ... } I.e., the actual parameter can be any instance of any class that implements Runnable. The code in the run method in that class is the code that will be run in the thread of computation.
SLIDE 27
Creating a Thread
Class Thread has a constructor that takes a Runnable as a parameter. in class Thread public Thread(Runnable r) { ... } I.e., the actual parameter can be any instance of any class that implements Runnable. The code in the run method in that class is the code that will be run in the thread of computation.
SLIDE 28
Starting a Thread
Class Thread has a method that will start a thread of computation. in class Thread public void start() { ... } This will tell the Java interpreter to start running the run method in that Thread, concurrently with any other Threads that have been started in this way. Note: We don’t call the run method directly: calling start() will register the thread of computation as a candidate for timeslicing; when the Java interpreter chooses that thread to actually run, it will execute the code in the run() method.
SLIDE 29
Starting a Thread
Class Thread has a method that will start a thread of computation. in class Thread public void start() { ... } This will tell the Java interpreter to start running the run method in that Thread, concurrently with any other Threads that have been started in this way. Note: We don’t call the run method directly: calling start() will register the thread of computation as a candidate for timeslicing; when the Java interpreter chooses that thread to actually run, it will execute the code in the run() method.
SLIDE 30
Starting a Thread
Class Thread has a method that will start a thread of computation. in class Thread public void start() { ... } This will tell the Java interpreter to start running the run method in that Thread, concurrently with any other Threads that have been started in this way. Note: We don’t call the run method directly: calling start() will register the thread of computation as a candidate for timeslicing; when the Java interpreter chooses that thread to actually run, it will execute the code in the run() method.
SLIDE 31
Example
We will write a program that prints out the numbers from 0 to 40 to standard output. We will then run two of these programs as separate threads. The output will show the effect of time-slicing.
SLIDE 32
Implementing Runnable
class NumberPrinter public class NumberPrinter implements Runnable { private String name; public NumberPrinter(String s) { name = s; }
SLIDE 33
Implementing Runnable
class NumberPrinter, contd. public void run() { for (int i=0; i < 40; i++) { System.out.println(name + ": " + i); } } }
SLIDE 34
Starting Threads
Our program will create two instances of NumberPrinter, and then start them running concurrently. We’ll create two instances of class NumberPrinter, with different ‘names’; then we’ll create two threads with those two NumberPrinters; then we’ll call the start() methods of those two Thread instances. (We won’t call the NumberPrinter#run() methods directly; the Java interpreter will do that for us when we call the start() methods.)
SLIDE 35
The main() method
in class NumberPrinter public static void main(String[] args) { NumberPrinter np1 = new NumberPrinter("John"); NumberPrinter np2 = new NumberPrinter("Jane"); Thread t1 = new Thread(np1); Thread t2 = new Thread(np2); t1.start(); t2.start(); }
SLIDE 36
The Output
terminal output John: 1 John: 2 John: 3 ... John: 39 Jane: 1 Jane: 2 ... Jane: 39
SLIDE 37
Inside the Java Interpreter
The Java interpreter keeps a list of ‘ready’ threads. These are: threads that have been started, have not yet finished, and are waiting to be chosen to run The Java interpreter chooses one thread from the ‘ready’ pool, and executes it for some period of time (called a ‘quantum’) then stops it, chooses another, etc., etc., until all threads have finished.
SLIDE 38
Run, John.
Our example starts with only the main thread running. In that thread, two threads are created (t1 and t2). One of these is started (t1.start()). Now there are two ‘ready’ threads: main and t1. Either main will continue to run (and start t2), or main will be stopped and t1 will run.
SLIDE 39
See John run.
If (or when) main is allowed to continue, it will start t2, and there will be two ready threads: t1 and t2. The main thread has finished, and is no longer in the ready-pool. We say the thread is dead. There is no reason why t1 should be chosen before t2: this depends solely on how multi-threading is implemented in the Java interpreter. It so happens — in this case — that t1 is started, and allowed to finish, before t2 is chosen to run.
SLIDE 40
Experimenting
By making the threads take longer to complete, different behaviours can be observed. On this machine, if the threads print out the numbers between 0 and 2000, then time-slicing can be seen in action: terminal output ... John: 1004 John: 1005 Jane: John: 1006 Jane: 1 ...
SLIDE 41
Leaving the Ready-Pool
The choice of which thread from the ready-pool to run is made by the interpreter. However, the programmer does have some influence. A thread can ‘pause’ by calling the Thread.sleep() method. It will stop running, and be put in a ‘blocked’ pool for the specified number of milliseconds.
SLIDE 42
Trying to Sleep
We introduce a call of sleep() to the run method in class NumberPrinter changing class NumberPrinter public void run() { for (int i=0; i < 40; i++) { System.out.println(name + ": " + i); try { Thread.sleep(10); } catch(InterruptedException e) { } } }
SLIDE 43
The Output
terminal output John: Jane: John: 1 Jane: 1 John: 2 Jane: 2 ...
SLIDE 44 Let Sleeping Threads Lie
When a thread calls the sleep(long) method, two things happen:
1
execution of that thread ceases for the specified number of milliseconds (the long parameter);
2
that thread is put into the ‘blocked’ pool for the specified number of milliseconds; another thread will be chosen from the ready-pool to run; once the specified number of milliseconds have elapsed, the blocked thread will be put back into the ready-pool.
SLIDE 45
Getting Back in the Running
Once it is moved back from the blocked pool to the ready-pool, the thread that called sleep() may be one among many in the ready-pool. There is no guarantee that that thread will recommence running after precisely the specified number of milliseconds. (In fact, it’s unlikely.) The sleep() method is a way of letting other threads have their chance at running, and making sure that one thread doesn’t monopolize the CPU.
SLIDE 46
Using Concurrency: Example 2
A common application of concurrency is in servers. Servers accept connections from remote clients; typically they receive some request from the client, and respond in some (application-dependent) way. Handling a request from a client may take some time, and while the server is busy processing and responding to the request, it is often desirable to allow other remote clients to connect to the server. I.e., it is often desirable to handle requests from several remote clients concurrently. Let’s revisit the EchoServer example from Lecture 21.
SLIDE 47
Using Concurrency: Example 2
A common application of concurrency is in servers. Servers accept connections from remote clients; typically they receive some request from the client, and respond in some (application-dependent) way. Handling a request from a client may take some time, and while the server is busy processing and responding to the request, it is often desirable to allow other remote clients to connect to the server. I.e., it is often desirable to handle requests from several remote clients concurrently. Let’s revisit the EchoServer example from Lecture 21.
SLIDE 48 Using Runnable
Summary of using Threads:
1
declare a class that implements Runnable (i.e., gives a concrete run() method);
2
create an instance of that class;
3
create an instance of Thread using the Thread(Runnable) constructor;
4
start the thread. We’ll do this for the session-handling part of EchoServer,
SLIDE 49
Handling Multiple Sessions
We could allow sessions to be handled concurrently by using threads: class ThreadedEchoServer { public static void main(String[] args) { // used to handle sessions Thread t; try { ServerSocket ss = new ServerSocket(8189);
SLIDE 50
Handling Multiple Sessions
class ThreadedEchoServer, contd. while (true) { Socket incoming = ss.accept( ); t = new Thread(new EchoHandler(incoming)); t.start(); } } catch (Exception e) { System.err.println(e.getMessage()); } } }
SLIDE 51
Handling Multiple Sessions
Our session-handling code goes into a class that implements Runnable: class ThreadedEchoHandler implements Runnable { Socket client; ThreadedEchoHandler(Socket s) { client = s; }
SLIDE 52 Handling Multiple Sessions
. . . so it must have a run() method: class EchoHandler, contd. public void run() { try { BufferedReader in = ...; PrintWriter out = ...;
....");
// as before... } } } Note: when the user ends the session, the thread dies.
SLIDE 53
Using telnet
Again, we can connect to the server using telnet. Because the server creates threads to handle each session, we can have multiple telnet sessions all connected to the server at the same time.
SLIDE 54
Adding State to the Server
The ThreadedEchoServer is very simple. Most servers either control access to some data (e.g., web servers), or allow remote clients to add data (e.g., message board servers). We can add some data to the ThreadedEchoServer by keeping count of how many clients connect to the server, how many clients are currently connected, and how many I/O exceptions have been thrown. We’ll develop a class to keep count of connections and exceptions, then the server will: create an instance of this class, and ‘share’ this instance with all the session-handling threads, so that it can keep track of all connections and exceptions
SLIDE 55
Keeping Count
class ConnectionCounter class ConnectionCounter { private int connections = 0; private int currentConnections = 0; private int exceptions = 0; public int getConnections() { return connections; } public int getCurrentConnections() { return currentConnections; } public int getExceptions() { return exceptions; }
SLIDE 56
Keeping Count
class ConnectionCounter class ConnectionCounter { private int connections = 0; private int currentConnections = 0; private int exceptions = 0; public int getConnections() { return connections; } public int getCurrentConnections() { return currentConnections; } public int getExceptions() { return exceptions; }
SLIDE 57
Keeping Count
class ConnectionCounter class ConnectionCounter { private int connections = 0; private int currentConnections = 0; private int exceptions = 0; public int getConnections() { return connections; } public int getCurrentConnections() { return currentConnections; } public int getExceptions() { return exceptions; }
SLIDE 58
Keeping Count
class ConnectionCounter class ConnectionCounter { private int connections = 0; private int currentConnections = 0; private int exceptions = 0; public int getConnections() { return connections; } public int getCurrentConnections() { return currentConnections; } public int getExceptions() { return exceptions; }
SLIDE 59
Keeping Count
class ConnectionCounter, contd. public void addConnection() { connections++; currentConnections++; } public void endSession() { currentConnections--; } public void countException() { exceptions++; } }
SLIDE 60
Passing References
The server will create a ConnectionCounter instance, but it’s the session-handling threads that need access to the counter. How do we let each thread use the counter? in the server class public static void main(String[] args) { ConnectionCounter stato = new ConnectionCounter(); while (true) { Socket incomming = ss.accept(); new Thread( new StatHandler(incoming, stato) ).start(); ...
SLIDE 61
Passing References
The server will create a ConnectionCounter instance, but it’s the session-handling threads that need access to the counter. How do we let each thread use the counter? in the server class public static void main(String[] args) { ConnectionCounter stato = new ConnectionCounter(); while (true) { Socket incomming = ss.accept(); new Thread( new StatHandler(incoming, stato) ).start(); ...
SLIDE 62
Passing References
The server will create a ConnectionCounter instance, but it’s the session-handling threads that need access to the counter. How do we let each thread use the counter? in the server class public static void main(String[] args) { ConnectionCounter stato = new ConnectionCounter(); while (true) { Socket incomming = ss.accept(); new Thread( new StatHandler(incoming, stato) ).start(); ...
SLIDE 63
Passing References
The server will create a ConnectionCounter instance, but it’s the session-handling threads that need access to the counter. How do we let each thread use the counter? in the server class public static void main(String[] args) { ConnectionCounter stato = new ConnectionCounter(); while (true) { Socket incomming = ss.accept(); new Thread( new StatHandler(incoming, stato) ).start(); ...
SLIDE 64
Storing Passed References
the session-handler class class StatHandler implements Runnable { private Socket client; private ConnectionCounter counter; StatHandler(Socket s, ConnectionCounter cc) { client = s; counter = cc; } Store the passed reference to the Socket Store the passed reference to the ConnectionCounter
SLIDE 65
Storing Passed References
the session-handler class class StatHandler implements Runnable { private Socket client; private ConnectionCounter counter; StatHandler(Socket s, ConnectionCounter cc) { client = s; counter = cc; } Store the passed reference to the Socket Store the passed reference to the ConnectionCounter
SLIDE 66
Storing Passed References
the session-handler class class StatHandler implements Runnable { private Socket client; private ConnectionCounter counter; StatHandler(Socket s, ConnectionCounter cc) { client = s; counter = cc; } Store the passed reference to the Socket Store the passed reference to the ConnectionCounter
SLIDE 67
Being Principled
Principle If an instance of one class needs to use an instance of another class, use the constructor to pass the required instance, and store it in a field.
SLIDE 68
Using Passed/Stored References
session-handler class, contd public void run() { counter.addConnection(); ... try { // set up I/O streams ...
SLIDE 69 Using Passed/Stored References
session-handler class, contd. while (true) { line = in.readLine(); ... if (line.equals("0s")) {
" + counter.getConnections());
" + counter.getCurrentConnections());
" + counter.getExceptions()); } }
SLIDE 70
Using Passed/Stored References
session-handler class, contd. } catch(IOException ioe) { counter.countException(); } finally { // shut down streams and socket ... counter.endSession(); } } }
SLIDE 71
Summary Threads interface Runnable multi-threaded Servers Next:
loose threads