CM10228 / Programming II:   Lecture 9


More on Threading 
most especially
When Threading Goes Wrong


-I. Getting Help You Need and More About Class Class

  1. Some of you can't really program in Java, yet.
    1. We know that.  One of the things that this course is about his helping you catch up.
    2. But this implies you need to spend more time, since you also have course content to keep up on.
      1. Remedial lectures are for people who can't program, NOT other people who want even better marks.
    3. You should be going to labs, maybe even more than just your own lab.
    4. You should be asking for one-on-one help with even the most basic questions.
    5. Bring your CW1 into Lab this week, even though it hasn't been marked yet, and ask for help getting it to really work.
    6. If your code mostly works but you'd like higher marks, talk to tutors about that too -- maybe wait for the marks back though.
    7. You may not get exactly identical marks for identical code!
      1. You are making more complicated programs than you were at A level, and last term.  Mark schemes are no longer well defined.
      2. We don't want your mark to be based on guessing games.  If you came up with a reasonable interpretation of the spec, we want to give you credit.
      3. Do not come asking for remarking for less than 10 marks -- probably not for less than 20.  
        1. You can be marked down as well as up.
        2. It's OK to ask for clarification about basic confusions, but arguing about less than 1% of your mark is not worth anyone's time, including yours.
      4. If you get < 30 points, consider resubmitting "late".
        1. You may learn more
        2. If you get your CW mark up to near 40%, you only need 40% on the exam to pass.
  2. Reminder from the Space, Class & Interface lecture–  For every class that you use in a program, there's an instance of the class Class.
    1. This makes sense, because where else would the class variables go?  Variables live in objects / instances.
  3. You can do cool things with class Class, e.g.
    1. Thing I showed you – Find out the class of an object you've been passed.
      String s = "Arvice";
      Class myclass = s.getClass();
      System.out.println(myclass.getName()); // prints "java.lang.String"
    2. New things–
    3. Get the class object of something you've heard of.
      try {
      Class sneakersClass = Class.forName("Sneakers");
      } catch (ClassNotFoundException e) {e.printStackTrace();}
    4. Use the class object to make an instance (assuming we have a good type for the object, in this case an interface...)
      interface Shoe {...
      }

      Shoe shoeish = (Shoe) sneakerClass.newInstance();
  4. These sorts of things are particularly useful if you have been given new code from somewhere, e.g. a plugin from the internet.
  5. Another related issue (for the keen): Reflection.  See O'Reilly's "Learning Java" by Niemeyer & Knudsen, Chapter 7.
    1. For the less keen, the important thing is to realize that interfaces don't have objects.  They are always purely specifications.

I. More Threading Basics:  Scheduling & Interruption

  1. What are threads for?
    1. A thread is a flow of control within a program.
      1. This really is basic.  In fact, it's basically covered in the first threading lecture.
    2. Every program has at least one thread. 
      1. The main program thread works just like we've been describing.
      2. It can be blocked!
    3. Threads let the rest of your program get useful stuff done while parts of your program are doing slow things, like waiting for users or disk I/O.
    4. Threads also let you attend to other tasks neatly without confusing your code.
      1. E.g. as from last lecture -- updating the clock, autosaving.
      2. If you had to put something in every method of your code to check if it was time to do one of those tasks, that would be very messy.
      3. The scheduler effectively does this for you.
  2. Threads are managed by a scheduler.
  3. The scheduler picks a runnable thread to allow to run for a short slice of time.
  4. A thread is runnable when it is in the ready state.
    1. Remember from last week.
    2. Basically 
      1. has been started
      2. hasn't died
      3. isn't blocked, asleep or waiting.
  5. There's no guarantee about the order in which threads are taken --- they're just taken from a queue.
  6. The death of threads:
    1. Threads will die when they just finish --- when their run function ends / returns.
    2. They can also be terminated when they get notified that they should interrupt.
      1. This is done by calling the thread's interrupt function, e.g. thread.interrupt();
        1. This won't kill the thread directly, rather it issues a polite request to the thread to die!
        2. See the examples below for how the thread should watch for interruptions.
      2. Note on language: this isn't like an interruption in a conversation, where you might go back to what you are doing, this is (at least intended to be) death.
        1. Language is inherited from unix -- Java was written by Sun.
        2. Interrupt in unix/linux can be generated from the keyboard with ^C,  "control c".
        3. Originally Java allowed you to send suspend (^Z) and resume messages, but these have been deprecated as unnecessarily complicated.
        4. If you want your thread to be able to be suspended, write a method so that it can put itself to sleep!
      3. A thread's run method should check occasionally to see if it has been interrupted:
        1. Interrupts are easy to catch when you're sleeping:
          // a silly run function that's just looking to be interrupted!
          public void run() {
          try {
          Thread.sleep(5000);
          System.out.println("FAIL: No Interrupt! Returned from sleep.");
          } catch (InterruptedException e) {
          if (isInterrupted()) {
          System.out.println("I've been interrupted;"+
          " If I'd been doing anything now I'd clean it up.");
          return(); // return after finish cleaning up!
          }
          }
          }
        2. But if you're busy doing something, you need to explicitly give the system a chance to check messages!
          // a tight loop would ignore interrupts, so we added a short sleep
          public int run() {
          int mySum = 0;
          for (int iii = 10000000; iii > 0; iii--) {
          mySum += iii;
          if (iii mod 10000 == 0) {
          try {
          Thread.sleep(20); // twenty milliseconds!
          } catch (InterruptedException e) {
          if (isInterrupted()) { // moderately redundant, see below
          System.out.println("I've been interrupted;"+
          " I only got to " + iii);
          return(-1); // return after finish cleaning up!
          } // if interrupted
          } // catch interrupt
          } // once in 10,000 steps check
          } // for a long time run a tight loop
          return(mySum);
          } // method run

          1. A tight loop is one with no sleeps, waits or calls to other classes' methods.  It's not always easy to guess, but basically it's things the compiler will try to wrap very tightly and get over with quickly.
          2. When a program hangs (stops doing anything), it's generally either blocked or stuck in a tight loop that isn't terminating for some reason.  If it is blocked waiting for some I/O, an interrupt should stop it (depends on the OS!)
      4. isInterrupted() checks the status of the interrupt flag without changing it.
        1. if you use interrupted() instead, you will not only find out if you were interrupted, but clear the flag as well.
        2. This means you really could just continue on & wait until you're interrupted again.
        3. So really, interrupt can mean different things in different applications (you'll see this occassionaly).
        4. But normally, people expect things to die if they are interrupted.

II. Corruption, Locking & Synchronization

  1. As we talked about in the last lecture, threads can share state. 
    1. e.g. if every instance of a class has a thread, then they share access to the class variables.
  2. This can be a problem!
  3. The classic example:  An ATM (automatic teller machine).
    1. Suppose I and my partner are standing at two ATMs right next to each other.
    2. I want to transfer money from our checking account to my savings account.
    3. My partner wants to take money out of my checking account.
  4. Suppose that these are the threads that get run:
    //My Thread
    transferAmount = ATM.getTypedNumber();
    float checkingTotal = checking.getBalance();
    float savingsTotal = savings.getBalance();
    // would really have to catch if this makes the checkingTotal < 0!
    checkingTotal -= transferAmount;
    savingsTotal += transferAmount;
    checking.setBalance(checkingTotal);
    savings.setBalance(savingsTotal);

    // My Partner's Thread
    withdrawalAmount = ATM.getTypedNumber();
    float checkingTotal = checking.getBalance();
    // would really have to catch if this makes the checkingTotal < 0!
    checkingTotal -= withdrawalAmount;
    ATM.squirtMoneyOut(withdrawelAmount);
    checking.setBalance(checkingTotal);
     
  5. What makes ATM examples interesting is the squirtMoneyOut command.
    1. Once the customer has the money, nothing the program can do will get it back!!
    2. From a computer science perspective, squirting money is a side effect, but it has irrevocable consequences in the real world.
  6. Now suppose that both of our threads read the original checking balance from before either of us has changed it.
    1. We get free money! 
    2. The final checking balance will only reflect either the withdrawal or the transfer, not both!
    3. We have more money in savings and my partner has cash too.
  7. This may sound cool, but actually it's not.
    1. If we'd been depositing money, we could have lost money in the same way.
    2. No nation / economy can do very well if their banks don't work better than this!
  8. The solution is called locking
    1. If a thread is going to do multiple things to some memory / state / a variable (esp. read it then change it!) then it locks that variable.
    2. A lock prevents other threads from accessing the value.
    3. If they try to, they block - basically they wait until they can get access to it.
  9. In Java, for some reason a lock is called a semaphore -- here's some notes on the topic.
      1. the basic idea with a lock is that every object threads might share is associated with exactly one lock.
      2. only one thread can hold the lock at a time.
      3. It is important to hold the lock for as short of a time as possible, and then release it.  Otherwise, you make other threads wait and slow down the program.
      4. E.g. if my partner had to wait for money while I was looking at our balance on screen.
  10. Originally, all locking in Java was done with implicit (vs explicit) locks via synchronization.  This is still an available mechanism, so I'll show it to you here.
  11. There are two ways to use synchrony:
    1. synchronized methods, and
    2. synchronized statements.
  12. Only one thread can call synchronized code on an object at a time.  One way to do this is by declaring synchronized methods:
    public synchronized float debitAccount (Account a, float amount) {
    if (a.getBalance - amount < 0) {
    throw new BalanceLTZeroException ("some clever message");
    }
    a.setBalance(a.getBalance() - amount);
    return (a.getBalance());
    }
  13. Notice that I haven't only solved the problem by creating a synchronized method.  
  14. I also had to create essentially a new way of accessing the account balance.  All other accessors should either be synchronized, made private or got rid of!
    1. This is because we haven't really locked the attribute, we've locked a method.
    2. If you want to lock just individual elements of data rather than code, you need to use a database (more on this next year!)
  15. If your method is long you may not want to declare the whole thing synchronized.
    1. Don't want to cut down on parallelism.
    2. Want to let other threads have a go.
  16. The synchronize statement is another way to create synchronized code.
    float checkingTotal;
    synchronize (checking) {
    checkingTotal = checking.balance();
    checkingTotal -= transferAmount;
    checking.balance(checkingTotal);
    }
    System.Out.printline("You have "+ checkingTotal +" in your checking account");
    1. Notice synchronize in this context takes an argument (an object)
      1. Every object has an implicit lock, which is what locks when you call a synchronized method.
      2. If you use that object in the synchronize statement, it will also lock any other access to that object with synchronized code using either way of synchronizing.
      3. You can also create objects just to use their locks if you want to have finer-grained locking.
    2. Notice: you can still get at the object if you use unsynchronized code!
    3. So in other words, this only blocks access from synchonized methods of the object, or of other (or the same!) synchronized program blocks.
    4. Java locks code, not data.
      1. You have to synchronize a lot of things!
      2. Most people wind up using databases to address this (see next year.)
  17. Again, you don't want to do this very often or for very long bits of code, because that will reduce the benefit of having threads in the first place.

This is how far we got in class in 2006... but we got through it all in 2007 (from here took 10 minutes.) Finished OK most years if skipped Class Class.  2015 finished early even including Class Class, partly by doing a lot of the code from the laptop projector.

III. Liveness & Deadlock

  1. Locking sounds great, right?  But what if my partner and I are running threads like this?
    //My Thread
    synchronize (checking) {
    synchronize(savings) {
    // do stuff to our accounts...
    }
    }

    // My Partner's Thread
    synchronize (savings) {
    synchronize(checking) {
    // do stuff to our accounts...
    }
    }
  2. If we are very unlucky, my thread will get just enough time on its first slice to lock checking, while my partner's will have just enough time to lock savings.
    1. From then on, whenever our threads get a slice, they will still be blocked!
    2. This is called deadlock.
    3. Important fact about luck and computers: computers do things fast enough and often enough that however unlikely something may be, if it's possible at all it will happen eventually.  Probably it will happen often!
  3. There are a lot of ways to avoid deadlock, but none of them are perfect!
    1. You can make an ordering of how locks should be acquired.  Then the above could never happen... if no mistake was ever made!
    2. You can have some process (e.g. the scheduler) notice when a process hasn't done anything for a very long time & interrupt it. 
      1. This lets the other process go.
      2. But it means you have to plan for the possibility that you may not finish your thread (should always think about that anyway.)
    3. You could make all the resources you need attributes of a larger object, then lock / synchronize that.
  4. Although it's important to allow for interrupts, the main way to avoid deadlock is with careful engineering and good design patterns.
  5. Note that even if you aren't entirely deadlocked, you can be blocked a lot of the time if everything is synchronized.
    1. Not being blocked too much is called liveness.
    2. If you don't have much liveness then there's not much point in using threads!

IV. Other Threading Notions

  1. Thread groups & security:
    1. Unless you say otherwise, thread is grouped with its parent (the thread that called it.)
    2. Threads groups are used to determine security --- if you are running on the internet, you might not want just any thread to be able to manipulate you!
    3. See article listed below in summary.
  2. Livelock:
    1. Sort of like deadlock, but the difference is that the threads keep waking up, but can't really do anything and go back to sleep.
    2. Harder to detect by the scheduler than deadlock because it looks like they are doing something, they aren't just blocked.
    3. Has to be dealt with through engineering.
    4. Have a look on the discussions about Liveness on the Sun Oracle Java Tutorial listed below.
  3. The producer / consumer pattern.
    1. Helps you deal with two things happening at different rates.
    2. Something may produce information / signals & another thing needs to process / `consume' that data, but at a different rate.
    3. Implementing this pattern involves using wait / notify.
    4. Sun Oracle recommends doing this using Guarded Blocks.
  4. More sophisticated locking in Java  Don't mention in lecture, won't be on exam.
    1. Allows you to work with an explicit lock object.
    2. Is both more powerful & more complicated.
    3. The problem with complexity in locks is deadlock, livelock.
    4. The interface Lock documentation is interesting (& may help you understand implicit locks better.)
    5. Probably this is archaic now that there are semaphores, sorry I ran out of time & haven't checked.

V. Summary

  1. Threads are a huge topic we are barely touching.  If you are curious here's some more links:
    1. Usenix article about threads, thread groups & thread management.
    2. developers discussing how tight is too tight for a loop.
    3. Most useful resource: the Sun Oracle Java Tutorial on Threads.
  2. The most important things in this lecture have been:
    1. How to deal with interrupts, avoiding overly tight loops.
    2. The notion of locking and the mechanics of using synchronize
      1. on methods,
      2. on objects.
    3. The notion of deadlock - how does it happen?  What does it mean?  What helps avoid it?

page author: Joanna Bryson
  3 March 2015