CM10228 / Programming II:   Lecture 8


Threading and Concurrency


-I  Testing, Scaling & Iterative Design 1

  1. Systems Engineering: Writing a complex program:
    1. Think of an overall design -- parts you think you need.
    2. Choose one part that you can build that will work by itself.
    3. Test that part, make sure it works.
    4. Go back to your design:
      1. Did the part turn out like you thought?   Is it doing more or less than you expected?
      2. Fix the design in light of what you have really been able to build.
      3. Are any of the other parts going to be able to reuse some of the code you've written?
      4. Refactor your code for reuse.
      5. Pick the next part to do.
      6. Test both parts
        1. Make sure you haven't broken the first part!
        2. Make sure the second part works.
        3. If finished, win!
        4. else GOTO I.1.4.1
  2. Everyone should be able to get good marks on the test plan!  Just think about it first!
    1. Same about commenting.  Where do comments go?
      1. every method & class gets commented
      2. any tricky algorithms get commented
  3. Everyone should actually read the coursework spec all the way through, at least twice.
    1. once before you start.
    2. once when you've been working on it for a while & understand the problem better.
    3. maybe two more times before & after you've done the write up to maximise your marks.
  4. No one can anticipate everything when engineering a system -- even where that system is a single program with multiple objects
    1. Must have a flexible strategy.
    2. See e.g. agile software development.

I. How Do Single-Processor Computers Do Multiple Things Simultaneously?

  1. Many computers only have one CPU.  Even computers with more than one CPU appear to do more things at once than they have processors.
    1. When you are at your computer, you might be editing a program while at the same time a clock is regularly changing it's reading.
    2. Instant messaging or mail alerts might make other things happen (bells, flashes, windows pop up.)
    1. Other things are happening in the background you don't see:  
      1. disk journaling
      2. virus checks
      3. Denial of Service attacks (Harvard story).
  2. A single CPU is really only doing one thing at a time.
  3. Computers work like movies: 
    1. movies:  an illusion of continuous motion while in fact pictures are being flashed at you faster than you can see.
      1. Good animation:  60 Hz,
      2. Bad animation:  8-10Hz.
      3. Events that occur within 50ms are perceived as simultaneous by people.
      4. most species have longer simultaneous windows than we do -- prob a language adaptation.
    2. computers: illusion of multiple things happening created by flicking CPU "attention" between different jobs
      1. Kind of like one person trying to cook a whole meal, all food done at once.
        1. helps that some elements, e.g. the stove hobs, keep doing things while you don't attend to them.
        2. screens & graphics cards also hold state, even provide motion without main CPU.
  4. So different tasks on the computer are being given attention in turns.
    1. Some things have very high priority, e.g. responding to a mouse click.
      1. You can sometimes write something that's so fast/easy for the CPU & set it to too high of a priority, such that it's very hard to kill!
      2. If you insert print statements for debugging to try to figure out what's wrong, the effect can go away, because printing to a device slows the loop down.
      3. unix/linux "nice" command -- root can do negative niceness.
    2. Some only run when the computer isn't doing anything else (run in background).
    3. Things that have the same priority share CPU, swapping control around between them.
      1. Delicate balance not wasting too much time in the swapping, but to make everything seem reasonably responsive.
      2. Again, you may notice that really large applications are slow to swap in and out, because they've been written to disk, they aren't just living in memory, since they've been inactive for a long time.
    4. Any application that waits for user input spends the vast majority of its time doing nothing.
      1. people are much slower than computers! 
      2. at least, their hands are slower than computers' processing.

II. Threads in Languages

  1. You may want to make your own program have multiple different processes.
    1. For example, you might want to write a tool that has a search routine in it, but also has a button that can stop the search if it seems to be taking too long, or if you want to reset the parameters.
    2. Let alone that you might yourself be writing a browser, a mail program, an operating system!  You're becoming computer scientists, it could happen!
    3. Actually, it will probably happen in your next course work.
  2. There are two ways to do this:
    1. use the operating system to spawn another process.
    2. use threading within your own program.
  3. Spawning:
    1. Call "fork" -- will create a perfect clone.
    2. Both programs will be at the same point in their execution.
    3. Have to check the return variable from fork to see whether you are the child (the new process) or the parent (the one that called the fork).
    4. Have to have all the code either program will need in both programs!
      1. Some examples for the truly keen (don't worry, this isn't on the exam!)
      2. Example of fork in C.
      3. Example of fork in Perl.
      4. Example of fork in Python.
  4. Threads are a more modern approach, a better abstraction.
    1. One of the reasons people use Java (another thing it does really well.)
    2. May well be doing the same thing (using the OS) but you don't have to worry about it, the Virtual Machine (VM) does.  Perfectly portable.
      1. A thread supported by the OS is called a kernel thread - a little more efficient, has more direct access to OS calls.
      2. A thread that's done independently of the OS is called a user thread - a little less efficient, since program has to replicate some of what the OS does.
      3. The Java VM will use kernel threads if it can (if the OS supports it.)  
      4. No difference to the programmer; only difference is how quickly things run.
  5. Three ways to create a thread in Java.
    1. Write a class that extends Thread (a class Java provides)
    2. Write a class that implements Runnable (an interface Java provides)
    3. Write an inner class that does either of these.
      1. Inner classes are mostly only used for threads & GUIs
      2. You probably haven't seen them yet.
      3. Didn't exist before Java 1.1
      4. A dominant design pattern for threads.
  6. We'll look at how to do each of these, but before we can try to build one, we need to understand a little better about what one is.

III. Thread Definitions

  1. A thread is a flow of control within a program.
  2. Threads can be thought of as sort of independent, but in fact they interact with each other in two ways:
    1. State
    2. Sharing the CPU
  3. Unlike processes (the things run by the OS), threads all operate on the same program state.
    1. If you give each instance of a class a thread, then they can all share access to the class variables.
    2. You can also have multiple threads in one instance!  In that case, they share access to the instance variables.
  4. The VM (or possibly the real machine) gives a thread access to the CPU for a little bit of time, called a slice.
  5. The thread does some processing, then gives up control again for one of several reasons:
    1. It may yield to another process.
    2. The system may make it time out , that is, declare it's slice has lasted long enough (more common than yielding!)
    3. It may block (wait for input or output from some device.)
    4. It may put itself to sleep -- in which case it will not take another slice until it's awoken (it will probably set an alarm to wake itself up!)
    5. It may wait for some event. 
      1. This is pretty much like sleeping -- in that case the event is the alarm.
    6. It may terminate execution (ends the thread).
  6. Another way to think about what a thread does is to think about what states it can be in, & how they are accessed.
    1. Do y'all know what a Finite State Machine is?
    2. States: new, ready, blocked, running, dead, inactive.
    3. new goes to ready when you call start().
    4. ready is sort of the home position everything else leads in & out of it (except dead.)
    5. a ready thread will go to inactive via sleep or wait.
      1. An inactive thread will go back to ready if it gets a wake up, notify or notify all signal.
    6. a thread will pass from ready to running if it gets a run signal.
      1. normally, a running thread will yield or time out, thus going back to ready
      2. sometimes a running thread may get blocked while waiting for I/O, but once that's completed it will be ready.
      3. one time a running thread will complete (or be killed!) in which case it is dead.
        1. Threads can be killed when they are inactive too.
        2. Can sometimes be hard to kill a blocked thread (I'm not sure why...).
    7. TODO draw a picture for this.

IV. Code Examples

  1. First let's just subclass Thread. 
  2. This is probably the simplest way we can do this, very clear where the functionality is coming from.
    import java.util.Date;

    /**
    * @author joanna
    *
    * Demonstrate making a thread per class.
    * This gets called by GreetingThreadRunner
    */
    public class GreetingThread extends Thread {

    private String greeting;

    public GreetingThread (String aGreeting) {
    greeting = aGreeting;
    }

    private static final int REPETITIONS = 10;
    private static final int DELAY = 1000;

    public void run () {
    try {
    for (int i=1; i<= REPETITIONS; i++) {
    Date now = new Date ();
    System.out.println(now+ ": " + greeting);
    sleep (DELAY);
    }
    } catch (InterruptedException e) {
    System.out.println("Goodbye! from: " + e);
    } // catch
    } // run
    }

    /**
    * @author joanna
    *
    * This just runs GreetingThread -- how silly!
    */
    public class GreetingThreadRunner {


    public static void main(String[] args) {
    GreetingThread t1 = new GreetingThread("Hi There!");
    GreetingThread t2 = new GreetingThread("Ni hao!");
    GreetingThread t3 = new GreetingThread("Hullo, jolly good, what?");

    t1.start();
    t2.start();
    t3.start();
    }
    }
  3. Whether you subclass Thread or implement Runnable, you will need to create a run function.
    1. Well, really, Thread isn't abstract, so it does have a run, but it doesn't do anything!
  4. To start the thread, you have to have it call start();
  5. That's it!  That's all you really need! 
  6. Now the second way to thread is to make the class implement Runnable.  This is a bit more complicated, since we'll still need a thread...:
    import java.util.Date;

    /**
    * @author joanna
    *
    * To change the template for this generated type comment go to
    * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
    */
    public class GreetRunnable implements Runnable {

    private String greeting;
    private Thread greetThread; // must have a thread around the place

    public GreetRunnable (String aGreeting) {
    greeting = aGreeting;
    greetThread = new Thread(GreetRunnable.this); // pass own class to Thread constuctor
    }

    private static final int REPETITIONS = 10;
    private static final int DELAY = 1000; // one second

    public void run () {
    try {
    for (int i=1; i<= REPETITIONS; i++) {
    Date now = new Date ();
    System.out.println(now+ ": " + greeting);
    Thread.sleep (DELAY); //now need to reference Thread for this!
    }
    } catch (InterruptedException e) {
    System.out.println("Goodbye! from: " + e);
    } // catch
    } // run

    /* and now we need a start...*/
    public void start () {
    greetThread.start();
    }
    } // class GreetRunnable
  7. But running it is just the same:
    public class RunRunnableGreeting {


    public static void main(String[] args) {
    GreetRunnable t1 = new GreetRunnable("Hi There!");
    GreetRunnable t2 = new GreetRunnable("Ni hao!");
    GreetRunnable t3 = new GreetRunnable("Hullo, jolly good, what?");

    t1.start();
    t2.start();
    t3.start();
    }

    }
  8. So it does exactly the same thing, but with more hassle.  So why would you use the interface?
    1. Make sure you remember to define run(); (probably not worth it!)
    2. More importantly, allow your class to inherit from some other type!  (e.g. a GUI element.)
  9. Finally, the third way to thread is to use an inner class.
    1. The outer function neither extends nor implements anything to do with threading.
    2. Still need a thread though, but when we create it, we pass it the new run method (which was all we had to write when we extended thread.)
  10. Here it is:
    import java.util.Date;

    /**
    * @author joanna
    *
    * OK, now do it with an inner class
    */
    public class GreetInner { // doesn't implement or extend anything...
    private String greeting;
    private Thread greetThread;

    private static final int REPETITIONS = 10;
    private static final int DELAY = 1000; // one second

    public GreetInner (String aGreeting) {
    greeting = aGreeting;
    greetThread = new Thread() { // inner class! extends Thread...

    public void run () { // the new def. of run is just like the previous ones
    try {
    for (int i=1; i<= REPETITIONS; i++) {
    Date now = new Date ();
    System.out.println(now+ ": " + greeting);
    Thread.sleep (DELAY); //now need to reference Thread for this!
    }
    } catch (InterruptedException e) {
    System.out.println("Goodbye! from: " + e);
    } // catch
    } // run()
    }; // our inner class def! (note it needs a semicolon)
    /* we have to start this in here, since it isn't known outside the scope of the method*/
    greetThread.start();
    } // our constructor

    }
  11. Since the constructor started the thread, the main gets simpler:
    public class RunGreetInner {

    public static void main(String[] args) {
    GreetInner t1 = new GreetInner("Hi There!");
    GreetInner t2 = new GreetInner("Ni hao!");
    GreetInner t3 = new GreetInner("Hullo, jolly good, what?");
    }
    }
  12. Actually, there's no reason in the world to have main in its own class in any of these examples!  You can just stick it in the original class definition unchanged.
  13. Threads don't have to be inserted in constructors this way --- often they are in other methods.
    1. E.g. if you push a button on a GUI, you may start a new thread

VI. Summary

  1. Concurrency is what happens whenever your computer seems to be doing many things at once.
  2. It's largely an illusion
    1. except some devices have their own processors / state
    2. some machines have multiple CPUs
    3. but most of what you see is done by slicing
  3. States & transitions for threads.
  4. Three ways to implement threads.
  5. Introduced inner classes.
  6. (Newbie lecture should be on static stuff)
    1. can't call a non-static method from a static object
    2. error if you can an instance method from main
    3. students shouldn't make everything static!  NOT THE SOLUTION

page author: Joanna Bryson
27 February 2013