Exam zone
Messages that take time. Usually we don’t consider the time it takes to send a message from one oject to another. In most OO languages that time is virtually instantaneous. That’s why we draw the message lines horizontally -- they don’t take any time. However, in some cases messages do take time to send. We could be trying to send a message accross a network boundary, or in a system where the thread of control can break between the sending and reception of a message. When this is possible, we can denote it by using angled lines as shown in Figure 4-9
Asynchronous Messages. Usually, when you send a message to an object you don’t expect to get control back until the recieving object has finished executing. Messagse that behave this way are called synchronous
messages. However, in distributed or multi-threaded systems it is possible for the sending object to get control back immediately, and for the recieving object to execute in another thread of control. Such messages are called asynchronous messages. Figure 4-11shows an asynchronous message. Note that the arrowhead is open instead of filled. Look back at all the other sequence diagrams in this chapter. They were all drawn with synchronous (filled arrowhead) messages. It is the elegance (or perversity, take your pick) of UML that such a subtle difference in the arrowhead can have such a profound difference in the represented behavior.
Listing 4-5 and Listing 4-6 show code that could correspond to Figure 4-11. Listing 4-5 show a unit test for the Log class in Listing 4-6. Note that the logMessage function returns immediately after queueing the message. Note also that the message is actually processed in a completely different thread that is started by the constructor. The LogTest class makes sure that the logMessage method behaves asynchronously by first checking to see if the message was logged but not processed, then yielding the processor to other threads, and finally by verifying the the message was processed and removed from the queue.
Listing 4-5 TestLog.java import junit.framework.TestCase; import junit.swingui.TestRunner; public class TestLog extends TestCase { public static void main(String[] args) { TestRunner.main(new String[]{"TestLog"}); } public TestLog(String name) { super(name); } public void testSend() throws Exception { Log l = new Log(System.out); l.logMessage("the message"); assertEquals(1, l.messages()); assertEquals(0, l.logged()); Thread.yield(); assertEquals(1, l.logged()); assertEquals(0, l.messages()); l.stop(); } }
Listing 4-6 Log.java import java.util.Vector; import java.io.PrintStream; public class Log { private Vector messages = new Vector(); private Thread t; private boolean running = false; private int logged = 0; PrintStream logStream; public Log(PrintStream stream) { logStream = stream; running = true; t = new Thread( new Runnable() { public void run() { while (running) { if (messages() > 0) { String msg; synchronized (messages) { msg = (String) messages.remove(0); } logStream.println(msg); logged++; } } } } ); t.start(); } public void logMessage(String msg) { synchronized (messages) { messages.add(msg); } } public int messages() { return messages.size(); } public int logged() { return logged; } public void stop() throws InterruptedException { running = false; t.join(); } }
This is just one possible implementation of an asychronous message. Other implementations are possible. In general, we denote a message to be asynchronous if the caller can expect it to return before the desired operations are performed.