Make Room for JavaSpaces, Part I

Ease the Development of Distributed Apps with JavaSpaces

by Eric Freeman and Susan Hupfer
November 15, 1999

First published in JavaWorld, November 1999
Summary
This article teaches you about a simple, yet powerful, new network and distributed programming tool: JavaSpaces. It explains the basic concepts of the JavaSpaces programming model and introduces the compact JavaSpaces API.

This article begins a second thread of the Jiniology series. In June, Bill Venners launched Jiniology with an overview of Jini technology -- a powerful new infrastructure for building and deploying distributed systems that are organized as federations of services. This thread, which will be featured every other month in this column, focuses on JavaSpaces, a core Jini service from Sun Microsystems that provides a high-level means of creating collaborative and distributed applications. If you're building applications with Jini, you'll want to know how to use JavaSpaces to coordinate the participants in a Jini federation. But it's also important to remember that you can use JavaSpaces separately from Jini, as a tool for building general distributed systems in Java. In either case, JavaSpaces is worth a look, because it can significantly ease the design and coding of distributed applications.

In this series, we will begin by introducing you to the unique JavaSpaces programming model, which is quite different from other network and distributed tools with which you might be familiar. In subsequent articles, we will cover the details of the JavaSpaces API and how you can use it to glue processes together into a distributed application, and describe how JavaSpaces interacts with other components of Jini. Throughout the series, you'll see that JavaSpaces is simple (the API consists of only a handful of operations), expressive (a large number of problems can be solved using JavaSpaces), and powerful (you can build sophisticated distributed systems with small amounts of JavaSpaces code).

Let's get started.

A New Distributed Computing Model

Building distributed applications with conventional network tools usually entails passing messages between processes or invoking methods on remote objects. In JavaSpaces applications, in contrast, processes don't communicate directly, but instead coordinate their activities by exchanging objects through a space, or shared memory. A process can write new objects into a space, take objects from a space, or read (make a copy of) objects in a space; Figure 1 depicts several processes (represented by Dukes) interacting with spaces using these operations. When taking or reading objects, processes use simple matching, based on the values of fields, to find the objects that matter to them. If a matching object isn't found immediately, then a process can wait until one arrives. In JavaSpaces, unlike conventional object stores, processes don't modify objects in the space or invoke their methods directly -- while there, objects are just passive data. To modify an object, a process must explicitly remove it, update it, and reinsert it into the space.


Figure 1. Processes use spaces and simple operations to coordinate activities
Copyright Sun Microsystems, Inc.

Spaces are object stores with several important properties that contribute to making JavaSpaces a powerful, expressive tool. Let's take a closer look:

  • Spaces are shared: Many remote processes can interact with a space concurrently -- the space itself handles the details of concurrent access, leaving you to focus on the design of the high-level protocols between your processes.
  • Spaces are persistent: Spaces provide reliable storage for objects. When you store an object in a space, it will remain there indefinitely until it is removed. You can also request a lease time during which an object should be stored. Once stored in the space, an object will remain there until its lease time (which can be renewed) is over, or until a process explicitly removes it. We will discuss leases in more depth later in this series.
  • Spaces are associative: Objects in a space are located via associative lookup, not by memory location or by identifier. Associative lookup provides a simple means of finding the objects in which you're interested according to their content, without having to know what the object is called, who created it, or where it is stored. To look up an object, you create a template (an object with some or all of its fields set to specific values, and the others left as null to act as wildcards). An object in the space matches a template if it matches the template's specified fields exactly. You'll see that, with associative lookup, you can easily express queries for objects such as "Are there any tasks to compute?" or "Are there any answers to the prime factor I asked for?"
  • Spaces are transactionally secure: JavaSpaces makes use of Jini's transaction service to ensure that an operation on a space is atomic (either the operation is applied, or it isn't). Transactions are supported for single operations on a single space, as well as multiple operations over one or more spaces (either all the operations are applied, or none are). As you will see later in the series, transactions are an important way to deal with partial failure.
  • Spaces let you exchange executable content: While in a space, objects are just passive data -- you can't modify them or invoke their methods. However, when you read or take an object from a space, a local copy of the object is created. As with any other local object, you can modify its public fields and invoke its methods, even if you've never seen an object like it before. This capability gives you a powerful mechanism for extending the behavior of your applications through a space.

As this series progresses, we will show you how these properties play a key part in letting you create distributed applications that work well in the Jini environment, where networking is often spontaneous, and processes join and leave the computation dynamically, sometimes because of device or network failure.

Origins of JavaSpaces

We've described JavaSpaces as a new distributed computing model, but its origins can be traced back to Yale University in the early 1980s. There, Dr. David Gelernter developed a tool called Linda for creating distributed applications. Linda consists of a small number of operations combined with a persistent store called a tuple space. These operations are orthogonal to any particular programming language; they are part of a coordination language that can be added to any other computation language. The result of the Linda research was surprising: by using an object store along with a small number of simple operations, you can easily implement a large class of parallel and distributed problems using techniques that alleviate many of the pitfalls of building networked systems. In other words, space-based systems are not only simple (requiring only a few operations), but also expressive (lending themselves well to solving many distributed problems).

Dr. Gelernter's work inspired Sun's JavaSpaces service, and also influenced the design of the lookup and discovery components of the core Jini technology (which you'll see as the Jiniology series progresses). While JavaSpaces inherited the space model from Linda, the designers of JavaSpaces have updated the model in significant ways, leveraging the power of Java objects, Jini, RMI, and object serialization.

JavaSpaces in Context

Our description so far has been a little abstract, so let's consider a few examples of real distributed applications that you can model as processes exchanging objects through spaces.

Chat systems
Consider a simple multiuser chat system, in which a space serves as a chat area that holds all the messages making up a discussion. To talk, a participant deposits message objects into the space. All chat members wait for new message objects to appear, read them, and display their contents. Late arrivals can examine the existing message objects in the space to review previous discussion. In fact, since the space is persistent, a new participant can view the discussion long after everyone else has gone away, and participants can even come back much later to pick up the conversation where they left off. The list of chat participants can also be kept in the space and updated whenever someone joins or leaves the conversation.

Compute servers
Now consider analyzing realtime radio telescope data for signs of extraterrestrial life (much as the SETI@home project does). Such data is voluminous, and analyzing it is a computationally intensive job that is well suited to parallel computation by a network of computers -- in other words, a "compute server." Using the JavaSpaces technology, a series of tasks -- for instance, one task per chunk of data that needs to be analyzed -- is written into the space. Each participating computer searches the space for a task, removes it, completes the necessary computational work, drops the result back into the space, and then continues to look for more tasks. This approach scales naturally: it works the same way whether there are 10 computers available or 1,000. The approach also provides natural load balancing, since each worker picks up exactly as much work as it can handle in a given time, with slow computers doing less work and fast computers doing more.

Broker systems
As a third example, consider an online auction system that brings buyers and sellers of goods and services together. Suppose you, as a potential buyer, describe the item (such as a car) you'd like to buy and the price you're willing to pay, wrap the information in an entry, and write the resulting wanted-to-buy entry to a space. At the same time, potential sellers continually monitor the space for the arrival of wanted-to-buy entries that match items in their inventory. For example, Mazda dealers monitor the space for entries that describe Mazdas, while used-car dealers monitor the space for all used-car requests. When a matching request is found and read, a potential seller writes a bid entry into the space, stating an offering price. As a potential buyer, you continually monitor the space for bids on your outstanding requests, and, when you find one that's acceptable, you remove the bids and contact the seller (possibly through the space via another entry).

A Brief Overview of the API

Now it's time to introduce the JavaSpaces API. As we've already said, it's simple; in fact, in the remainder of this article we will cover everything that you need to know (barring some minor details) about it. However, before we describe the JavaSpace interface and its methods, we first need to talk about entries.

Entries
An object that is stored in a space is called an entry. To be an entry, an object just needs to implement the Entry interface. As an example, let's define a message entry that you can write into a space:


    import net.jini.core.entry.Entry;

    public class Message implements Entry {
        public String content;

        // a no-arg constructor
        public Message() {
        }
    }

Here we've defined a Message class with a string field that will hold the content of the message. Because we want to use this class with spaces, we need to implement the interface net.jini.core.entry.Entry, which is found in the package net.jini.core.entry. It's important to point out that Entry is a marker interface; in other words, the interface contains no constants or methods and therefore requires no special work to implement, other than adding implements Entry to your class definition.

Besides implementing the Entry interface, there are a few other conventions that our entries must follow. We'll have more to say about the reasons in later articles, but for now we'll just look at the broad outlines. An entry must have a public constructor that takes no arguments (a so-called no-arg constructor); this requirement stems from the underlying serialization that occurs when entries are transferred in to and out of spaces. Note that our definition of Message contains a no-arg constructor. Another convention is that fields of an entry should be declared public; this lets other processes find your entries in spaces via associative lookup, based on the values of those fields. A third convention is that fields of an entry must contain references to objects, rather than primitive types (that is, if you need to define a primitive type field such as int, you should use the corresponding wrapper class Integer instead). To make sure you are covering all your bases in defining entries, we recommend that you refer to JavaSpaces Principles, Patterns, and Practice,or to the Sun Microsystems JavaSpaces Specification for details. We will also, as mentioned, touch on some of the finer points in later articles.

Other than these requirements, an entry is like any other Java class; you can instantiate it, invoke its methods, and assign values to its public fields. Now that we've defined a Message entry class, let's see what operations are available for interacting with entries in spaces.

The JavaSpace interface
To interact with a space, you need to obtain access to an object that implements the JavaSpace interface. There are many ways of obtaining access to such an object (you can, for example, use the Jini lookup or the RMI registry) and we will cover the details of doing so in the next article. For now, we will concentrate on the JavaSpace interface itself.

The following methods are the bread and butter of the JavaSpaces API and are supplied by any object that implements the JavaSpace interface:

  • write: Places one copy of an entry into a space. If called multiple times with the same entry, then multiple copies of the entry are written into the space.
  • read: Takes an entry that is used as a template and returns a copy of an object in the space that matches the template. If no matching objects are in the space, then read may wait a user-specified amount of time until a matching entry arrives in the space.
  • take: Works like read, except that the matching entry is removed from the space and returned as the result of the take.

In addition, the JavaSpace interface supplies the following methods, which can be useful in many applications:

  • notify: Takes a template and an object and asks the space to notify the object whenever entries that match the template are added to the space. This notification mechanism, which is built on Jini's distributed event model, is useful when you want to interact with a space using a reactive style of programming.
  • snapshot: Provides a method of minimizing the serialization that occurs whenever entries or templates are used; you can use snapshot to optimize certain entry usage patterns in your applications. We will cover this method in detail in a later article.

That, in a nutshell, is the entire JavaSpaces API. Of course, there are a lot of details that we haven't covered here, and we will dive into them in the next article. For now, let's see how to code the obligatory "Hello World" example with JavaSpaces, and then wrap things up.

'Hello World,' JavaSpaces style

We'll implement our "Hello World" as an applet, using a convenience class called SpaceApplet, which is provided in Sun's Jini distribution. SpaceApplet handles the details of gaining access to a space for us via its space method. Here is our "Hello World" program:


import net.jini.core.lease.Lease;
import net.jini.space.JavaSpace;
import com.sun.jini.example.spaceApplet.SpaceApplet;

public class HelloWorld extends SpaceApplet {
    private JavaSpace space;

    public void init() {
        super.init();
        space = (JavaSpace)space();
        
        try {
            Message msg = new Message();
            msg.content = "Hello World";
            space.write(msg, null, Lease.FOREVER);

            Message template = new Message();
            Message result =
                (Message)space.take(template, null, Long.MAX_VALUE);
            msg(result.content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void msg(String str) {
        System.out.println(str);
    }
}

In the applet's init method, we first call super.init (the SpaceApplet's init method), which ensures that a SpaceApplet has access to a space. Then we call the space method, which returns an instance of an object that implements the JavaSpace interface (which we will refer to as a space object or space throughout this series).

With a space object in hand, we instantiate a Message entry (using the Message class we defined earlier in this article) and then assign "Hello World" to its content field. Now we are ready to write the entry into the space. To do so, we call the write method of the space, and pass the Message entry, null (the second parameter is used for specifying a transaction, and here we aren't using one), and a lease time of Lease.FOREVER (which asks the space to store this object indefinitely) to the method. The call to write results in placing one copy of the entry into the space; when the call completes, it's guaranteed that our entry is there.

Now that a copy of the entry is in the space, we can do two things with it: read it or take it out of the space. If we read it, we just obtain a copy of the entry in the space. If we take it, then we obtain a copy, but also remove the entry from the space so that no other process can obtain it. To do either, we need to construct a template that will match the entry. We will cover the specifics of matching in our next article, but it is an intuitive concept: a template matches an entry in the space if the objects have the same type and if, for every field the template specifies, that field matches the corresponding field of the entry. Unspecified fields (those with a value of null in the template) act as wildcards and will automatically match the corresponding fields of an entry in the space.

Continuing with our code, we next instantiate a template, which is another Message entry. We do not specify any fields (they are null by default), so our template will match any Message entry in the space. Alternately, we could specify "Hello World" in the content field, and our template would still match the one Message entry we stored in the space. If, on the other hand, we specify a content field of "Goodbye Cruel World" in our template, the template will not match our entry in the space.

Now that we have a template, we call take on the space, passing it the template, another null value for a transaction, and a length of time for the call to wait before returning. In this case, we specify a value of Long.MAX_VALUE, so the take will not return until a matching entry is found. At that point, the matching entry is removed from the space and returned as the result of the call, and we assign the value to the variable result. With this object in hand, we print out its content field and see this message:


Hello World

Looking Ahead

In this article we've gotten our feet wet with a brief look at the JavaSpaces model and API. The real power of JavaSpaces will become apparent as we dive deeper into the programming model, which uses entries combined with the key properties of the space to create communication and synchronization between processes. We will see that, when processes communicate and synchronize their activities through spaces rather than communicating with each other directly, they become loosely coupled. This loosely coupled nature of space-based programs is significant and powerful, and leads to flexible, powerful, scalable distributed applications -- and that is the whole point of Jini!

Running the Code

To get up and running with JavaSpaces programming, you will need to compile and run your code against the following packages from Sun Microsystems:

Resources


"Make Room for JavaSpaces, Part I" by Eric Freeman and Susan Hupfer was originally published by JavaWorld (www.javaworld.com), copyright IDG, November 1999. Reprinted with permission.
http://www.javaworld.com/jw-11-1999/jw-11-jiniology.html

Talk back!

Have an opinion? Be the first to post a comment about this article.

About the authors

-

Dr. Susanne Hupfer is director of product development for Mirror Worlds Technologies, a Java- and Jini-based software applications company, and a research affiliate in the Department of Computer Science at Yale University, where she completed her PhD in space-based systems and distributed computing. Previously, she taught Java network programming as an assistant professor of computer science at Trinity College. Susanne coauthored the Sun Microsystems book JavaSpaces Principles, Patterns, and Practice.