Adept Software Development

Adept: (A)pplication (D)evelopment (E)nterprise to (P)ersonal (T)ransition. It is a system I am developing to leverage Enterprise developer skills to produce stand-alone software for other market segments. This is a general software development blog discussing issues about project, architecture, design and development. The emphasis will be in Java, but many of the issues will be more general. Almost all will be technical.

http://marringtons.com

Wednesday, June 08, 2005

Synchronized - Dos and Don'ts

This is not a training session on multi-tasking in Java. I'll leave that hot potato for the more qualified technical writers. This is a starting set of guidelines for things to watch in code, that can be run by more than one thread, at any one time. In a J2SE environment this can only happen if you create and start threads. In any application server environment, from Tomcat to Weblogic, each client has at least one thread - and they can all be running in common code. So:
  • Do look closely at each and every piece of static data in the system. Threads can only clash when sharing static data, or data with a static element somewhere in the reference chain.
  • Don't put static data in library routines, as you can't be sure when they'll be used by threaded code. A classic problem is a cache, which is usually a synchronised static map. Because any data retrieved from such a cache has a static element in the reference chain, there is a risk of it being accessed by more than one thread. In my code this happened with the session cache. A single browser inside a single session can post two almost simultaneous requests serviced by two separate server threads - both accessing the same session data.
  • Do use syncronized to wrap any code that can be run by more than one thread and accesses common data. It will cause second and subsequent threads to wait until the first thread to have a syncronized block to leave said block.
  • Don't synchronize everything. It's a common beginner's mistake to synchronize everything in a class to forestall any problems. In the first case it won't stop problems as deadlocks are still possible, and in the second case it can make a program impossibly slow, by creating massive bottlenecks.
  • Do keep synchronised code sections to a minimum. I realise that technically this is the same point as the one above - but it's so important that it bears repeating. I can't believe how often I have seen synchronised methods that call logging functions. Logging, like disk or network I/O, can cause delays - not to mention the CPU time required to turn data into human readable form. If it's a popular piece of code, system response drops off while many threads wait for one to complete. Telltale symptoms of this mistake, then, are low CPU usage with long response times.
  • Don't use synchronised maps and lists unless they are absolutely necessary. There is no need to synchronise a map that's inside synchronised code in your application. Unlike items synchronised to the same semaphore, doing this will incur a considerable overhead to no advantage. The older Java containers were synchronised by default, whereas the new aren't.
  • Do think carefully on any synchronised code - because it's virtually impossible to unit test. Problems usually only manifest themselves in production with heavy varied load. These problems aren't easy to reproduce on request..
  • Don't assume that because code always works on a single-CPU desktop or test machine that it will on a multi-cpu server. Even desktops will be vulnerable once the new multi-core CPUs hit the market in quantity. A single CPU system is still linear; even when it looks like it's multi-tasking it's really just swapping between tasks very quickly. A multi-CPU system can have completely separate processors running the same code and accessing the same data. Certainly synchronize should work the same way in both cases, but the completely different mechanisms employed means that your code will be exercised differently. The end result is that code that works perfectly well on a single-CPU system may fail randomly on a 2 or 4 CPU server.
Enough, enough already. I think you get the gist: multi-tasking is a specialist field and any demonstration should come with a 'don't try this at home' tag. Application servers attempt to make the multi-tasking invisible at the expense of performance.

I always like to give an example. Unfortunately, thread awareness is such a complex issue that it's impossible to give a simple valid example. The best I could find in my code was in a double-hash database index. Don't try and understand the code out of context - download it at The Adept Library if you want to do that. This is an extreme case for minimising synchronised sections. If the method were synchronised it would lock out for a long time. Instead, only the bucket change is synchronised.

Note that the test is made twice - once to see if we need to do it and once inside the synchronised block to check that it has not changed. The odds of two threads deleting the same index at exactly the same time is probably billions to one, but it is not hard to protect against if you think on it. Because only the change is synchronised, care is taken in the rest of the code that reads are consistent even if the data under them changes. This takes thought, but is a lot more efficient than synchronising everything.

boolean delete( int hash, int record) throws IOException
  {
    int bucket = getBucket( hash);
    IntegerStack possibles = findInBucket( bucket, hash);
    int deleted = 0;
    int possible;
    while (! possibles.isEmpty())
      {
        possible = possibles.pop();
        if (index.file.getInt( possible) == record)
          {
            index.file.putInt( possible, -1);
            int secondaryBucketLocation
              = (hash >> primaryShift) & primaryMask;
            
            if (secondaryBucketCounts[secondaryBucketLocation] >= 0)
              synchronized(this)
                {  // only if sharing secondary bucket hash
                 if (secondaryBucketCounts[secondaryBucketLocation] >= 0)
                  {
                      secondaryBucketCounts[secondaryBucketLocation]--;
                      secondaryBucketLocation
                        = primaryBuckets[secondaryBucketLocation];
                      
                      if (secondaryBucketLocation == -1)
                        // in the middle of a split
                          secondaryBucketLocation = afterSplitLocation;
                      
                      index.file.putInt( secondaryBucketLocation,
                        index.file.getInt( secondaryBucketLocation) - 1);
                    }
                }
            deleted++;
          }
      }
    return deleted > 0;
  }

 

Thursday, June 02, 2005

Cooperative Multi-Tasking - Yesteryear and Today

I've been developing for and with multi-tasking systems since what feels like the dawn of time, but in actuality was the late seventies. Those were the last days of total dominance by mainframe or mini-computer on the desktop. Even the smallest computer was too expensive to be dedicated to one user or program. IBM were doing great things with virtual machines - making it appear as if each user had a whole IBM mainframe at their personal disposal. So-called minicomputers used multi-tasking so that individual applications (including the operating system itself) could have a share of CPU and memory.

Then came the micro-computer and PC. The users loved the freedom from expensive shared computers, but the first ones lacked the grunt for true multi-tasking - although Digital Research gave it a go with MPM and Concurrent CPM. This was when Microsoft first won the race by providing DOS to IBM. No multi-tasking. I can remember people starting spreadsheet calculations that would take up to two days - during which time their $5,000 computer could only be used as a paperweight.

Microsoft 'to the rescue' again with Windows 3 (the first non-geek popular version of Windows). Windows had originally been developed for the 8086 with little or no hardware support and an ability to make very small yet powerful programs. Only when the 80386 chip was available did system have memory and hardware support for multiple programs.

Windows 3 and it's even more popular networking offspring 3.11 were still built on the old 16 bit code. They inherited a multi-tasking method called co-operative multi-tasking. In this model a process or program truly does own the CPU until it calls an operating system service to release control and schedule the next task waiting for CPU. Those of us used to working with 'true' multi-tasking were fairly unimpressed with such a primative system. Yet, it worked amazingly well. This is because most programs spend most of their time waiting for user input. As long as they followed the well documented standard of having a primary loop that released the CPU for others while waiting, the used very little CPU.

And here's the rub. With the release of Windows 95 and true multi-tasking we see more inconsistent responsiveness and more user delays than in the 'primative' cooperative multi-tasking days. Even today with machines more than 10 times faster with almost 10 times as much memory, I can suffer noticable delays waiting for a web page to render while doing a large Java compile.

Certainly one reason is that we expect a modern computer to do many things at once, but the other reason - and the one I want to talk about here - is the sense of responsibility. With co-operative multi-tasking, the developer was overtly responsible for end-user performance. Even when doing a complex calculation or compile, a good developer would ensure that the system yielded control often enough so that the user interface did not become sticky.

With a return to the Unix fold of time-sharing multi-tasking, the responsibility goes back to the operating system and the developer is taught to ignore it. The former is good since the operating system can do a much better job of it - and it simplifies the code. The latter is not so good. The operating system attempts to discern user interface code and give it priority. This is why the mouse does not usually stick no matter how hard the computer is working. It's much harder to differentiate user interface calculations within a program from less important batch operations. This is why my browser rendering is delayed by the Java compile.

The resolution in native code is relatively obvious. Before a processor intensive operation, reduce the priority of the process with an operating system service and restoring it to normal when returning to interactive mode. This way other user interface programs should get priority and be less likely to be delayed.

This doesn't work if your code is further removed from the operating system. Don't mistake light-weight Java threads with OS level multi-tasking. It's light-weight because it shares the JVM cpu time between threads. Since the JVM is just a program to the operating system, it takes it's normal slice of computer resources and then divides it up among it's active threads.

The only way I can think of to release cpu resources to other programs during a long calculation or compile would be to sleep for one millisecond fairly regularly. I don't like it. Firstly it reeks of polling, and secondly your process will be unnecessarily delayed if no-one else needs the CPU.

In the case of a compile or build there is another option. They are such large processes that they can spawn their own independant JVM. It should be possible to tell the operating system to run these JVMs at a lower priority.

In truth modern CPUs are so over-powered that no-one except extreme power users would even notice. The larger bottleneck is around sharing disk access time rather than CPU time. This is a discussion for another day.