COMP 213 Advanced Object-oriented Programming Lecture 21 - - PowerPoint PPT Presentation

comp 213
SMART_READER_LITE
LIVE PREVIEW

COMP 213 Advanced Object-oriented Programming Lecture 21 - - PowerPoint PPT Presentation

COMP 213 Advanced Object-oriented Programming Lecture 21 Concurrency What is Concurrency? Concurrency is when two or more programs execute at the same time, either: sharing the same processor ( multi-threading ), or on different


slide-1
SLIDE 1

COMP 213

Advanced Object-oriented Programming

Lecture 21

Concurrency

slide-2
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
SLIDE 32

Implementing Runnable

class NumberPrinter public class NumberPrinter implements Runnable { private String name; public NumberPrinter(String s) { name = s; }

slide-33
SLIDE 33

Implementing Runnable

class NumberPrinter, contd. public void run() { for (int i=0; i < 40; i++) { System.out.println(name + ": " + i); } } }

slide-34
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
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
SLIDE 36

The Output

terminal output John: 1 John: 2 John: 3 ... John: 39 Jane: 1 Jane: 2 ... Jane: 39

slide-37
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
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
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
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
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
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
SLIDE 43

The Output

terminal output John: Jane: John: 1 Jane: 1 John: 2 Jane: 2 ...

slide-44
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
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
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
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
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
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
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
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
SLIDE 52

Handling Multiple Sessions

. . . so it must have a run() method: class EchoHandler, contd. public void run() { try { BufferedReader in = ...; PrintWriter out = ...;

  • ut.println("Hello!

....");

  • ut.flush();

// as before... } } } Note: when the user ends the session, the thread dies.

slide-53
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
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
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
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
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
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
SLIDE 59

Keeping Count

class ConnectionCounter, contd. public void addConnection() { connections++; currentConnections++; } public void endSession() { currentConnections--; } public void countException() { exceptions++; } }

slide-60
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
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
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
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
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
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
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
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
SLIDE 68

Using Passed/Stored References

session-handler class, contd public void run() { counter.addConnection(); ... try { // set up I/O streams ...

slide-69
SLIDE 69

Using Passed/Stored References

session-handler class, contd. while (true) { line = in.readLine(); ... if (line.equals("0s")) {

  • ut.println("Connections:

" + counter.getConnections());

  • ut.println("Currently :

" + counter.getCurrentConnections());

  • ut.println("Exceptions :

" + counter.getExceptions()); } }

slide-70
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
SLIDE 71

Summary Threads interface Runnable multi-threaded Servers Next:

loose threads