Java: Selected Topics

Java now provides facilities that make it efficient in implementing advanced applications, and even systems applications. It is not as efficient as C, in some cases by a factor of 4 or 5. But in most cases, with a little bit of care, much better is possible. Of course, to achieve higher level of efficiency, especially dealing with concurrency and networking, it is necessary to understand systems concepts as we encountered using Unix and to know the Java classes that express them. Here we touch on some of these classes. Essentially all the concepts and facilities we encountered in Unix are replicated in some form in Java. In fact Java now has classes, for example thread pools, that represent good ways of encapsulating system solution patterns, that can be adopted even when programming in C.
For a deeper understanding of the way that Java achieves its high performance you may look here
The JVM and associated technologies have made a fundamental contribution to our discipline since they provide an efficient virtual machine that runs essentially everywhere. This machine could be used as target for new languages (for example Scala) or old languages (for example JPython), making them portable by default.

Creating Threads

Threads can be defined by extending the Thread class or by implementing the Runnable interface. The former method is preferable if we plan to extend methods of the Thread class in addition to the run method. The latter method is preferable if that is not the case or we want to extend some other class, not Thread.

Here is an example of extending the Thread class, and here is the same behavior implemented using the Runnable interface.
To extend Thread in general we do something like

    class Foo extends Thread
    {
        .............
	void run() {
	    // here is the code we want executed by the thread
	}
	.............
    }
then we create an instance with
    Thread x = new Foo();
and start its execution with
    x.start();
which will start the new thread executing the run method.
To use Runnable we do something like
    class Foo implements Runnable
    {
        .............
        void run() {
            // here is the code we want executed by the thread
        }
        .............
    }
then we create a thread as follows
    Foo y = new Foo();
    Thread x = new Thread(y)
and again start its execution with
    x.start();
which will start the new thread executing the run method of y.
public void interrupt() is used to interrupt a thread. It will cause the InterruptedException or a ClosedByInterruptException if the thread was blocked on wait, join, or sleep, or on an I/O operation. Otherwise, i.e. if the thread is running the exception is not raised, the interrupt sets the status of the thread to interrupted, which the thread can determine by calling the isInterrupted() method, and the thread continues executing.

Traditional Synchronization

Traditionally synchronization in Java has been done using the synchronized attribute in conjunction with methods or blocks of code. Each Java object can be seen as a monitor with one condition variable. Methods or code that are qualified with the synchonized attribute on the same object will be executed in mutual exclusion. While executing such code one can relinquish control of the monitor and go to sleep by executing the wait method, to be waken up later with a call to notify or to notifyAll made by another thread now executing synchronized code [notify will wake one waiting thread, notifyAll will wake them all - but of course they will run only one at a time].

Here is an implementation for the problem of Producers communicating with Consumers though a Protected Bounded Buffer using the traditional synchronized approach.

Simple Sockets for Client-Server Interaction

TCP Client-Server interaction is easily done. On the server you create a ServerSocket (what we had called a listening socket)
ServerSocket listener = new ServerSocket(port);
then you accept a connection
Socket connected = listener.accept();
and establish the appropriate input and output streams on this socket
InputStreamReader reader = new InputStreamReader(connected.getInputStream());
OutputStreamWriter writer = new OutputStreamWriter(connected.getOutputStream());
Now you can read from reader and write to writer and then close the streams.
Here is a program that sends repeatedly buffers to a server, and here is the server program that sends back buffers. Both client and server print out information on the data rate of the connection. This program aims at sending/receiving as much data as possible using traditional socket mechanisms.

Explicit Synchronization with Locks and Conditions

For greater flexibility Java now (Java 1.5 and later) makes available explict locks (often called intrinsic locks) and associated conditions.
The Lock interface is implemented by two locks, ReentrantLock and ReentrantReadWriteLock. The word "reentrant" refers to the fact that these locks can be locked more than once by the same thread, i.e. one thread can lock .. lock and then unlock .. unlock. The word "ReadWrite" refers to a lock that implements the classic Readers and Writers behavior (i.e. concurrent multiple readers).
The ReentrantLock has a multiplicity of methods, as you can see from its API. Among them is public Condition newCondition() which returns a condition that will be associated to this lock [remember our discussion of monitors and of Pthread mutexes and conditions]. The basic methods of conditions are await, signal, and signalAll, that correspond directly to the wait, notify, and notifyAll which we saw with synchronized objects.

Here is another version of the Producers and Consumers problem using explicit locks and conditions.

LockFree Atomic Operations

Java supports the attribute volatile for variables. If we have said
volatile T x; //
It guaranties that if we write x, the write will go directly to memory, and if we read, it will come directly from memory. Thus updates made by a thread will become immediately visible to a concurrent thread. Beware that volatile applies to primitive types. In the case of reference type, the volatile applies to the refernce, not to the referenced object. Volatile does not give atomicity. In particular if we have said
volatile in x = 0;
..
x++;
the x++ operation may malfunction in a concurrent program because it is really 3 operations: read from x to a register, increment register, store to x.

For atomicity on simple variable Java gives us the package java.util.concurrent.atomic. In it we find AtomicBoolean, AtomicInteger, AtomicIntegerArray, AtomicLong, AtomicLongArray, AtomicReference, AtomicReferenceArray, plus field updaters.
Let's look at AtomicInteger.

    AtomicInteger x = new AtomicInteger(); // x initialez to 0
    AtomicInteger y = new AtomicInteger(3);// y initialized to 3
    int a = y.get(); // store in a value of y
    int b = y.getAndSet(5); // b gets old value of y, 3, and y is set to 5 
    int c = x.addAndGet(6); // value of x is incremented by 6 and returned
			    // now x has value 6
    int d = x.decrementAndSet(2)// value of x decremented by 2 and returned
			    // now x has value 4
    boolean updated = x.compareAndSwap(4,11); // If the value of x is 4,
			    // change it to 11 and return true; otherwise
			    // x is unchanged and false is returned
    x.set(21); // x is set to 21 
All these operations are atomic and lock free. They are user mode operations, thus no overhead for context switches to kernel.
Here is a threadsafe counter
    class Counter {
	private final AtomicInteger count = new AtomicInteger(0);
	public int increment() {
	    return count.getAndIncrement();
	}
    }
AtomicIntegerArray and similar array types allow atomic operations on each element of the array. AtomicIntegerFieldUpdater and similar field updaters allow atomic operations on volatile int fields of a class.

Java NIO

Traditional Java IO works well when processing files sequentially or communicating using blocking socket and associated threads. For frequent random accesses to files or for communication on hundreds of sockets it works less efficiently, and that is where the new Java IO, or NIO, comes into play. Fundamental concepts in NIO are buffers, channels, selection keys, and selectors. Selectors, selection keys, and channels allow us to reproduce the effect of the select statement in Unix. Namely we can block waiting for events at a variety of sources without needing to have a separate thread blocked waiting for each individual event. Also, interruptible channels, when closed will wake up any thread that might be blocked on that channel. Also, if instead such a thread is interrupted, the channel is automatically closed. Non-blocking IO is also supported.

Buffers

A Buffer can be created by allocation [allocate(int)], by wrapping an existing array [wrap(byte[])], or by asking the system to allocate the buffer outside of the JVM memory to reduce the number of copy operations [allocateDirect(int)]. In the first two cases the buffer will have a backing array, in the latter in may or not do so [find out by calling hasArray()]. We can have buffers for all primitive types except boolean. An extension of the ByteBuffer, the MappedByteBuffer, is available to achieve the effect of Memory Mapped IO. We will see examples of use of buffers when discussing channels.
For a buffer are defined the properties:
  1. capacity: the number of elements that it can contain.
  2. limit: it represents the high water mark allowed for reading or writing to the buffer
  3. position: like the cursor in Unix files, the current position where we can read or write
  4. mark: a position that can be remembered with the mark() operation and later reestablished as current position with the reset() call.
Among these properties holds the relation
0 <= mark <= position <= limit <= capacity
Reading and writing to/from a buffer is done using get and put operations, which can take place relative to the current position, or at absolute locations in the buffer identified by an index.

Initially position is 0, the limit is equal to capacity, and the mark is undefined. The clear() operation will empty the content of the buffer, set limit to capacity, position to 0 and mark is undefined. The flip() operation sets limit to the current position, position to 0, and mark is undefined. It is used to read out what was written into the buffer. The rewind() operation sets the position to 0, the mark is undefined, and limit is unchanged. The hasRemaining() operation tells us if the position is less than limit (thus we can write/read something to/from the current position).
Buffers are not threadsafe.

Channels, SelectionKeys, and Selectors

Channels, from the API: "A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing. ... Channels are, in general, intended to be safe for multithreaded access".
The channels of greatest interest are FileChannel, SocketChannel, and ServerSocketChannel. They are all interruptible in the sense indicated earler, that a thread blocked on this channel will receive a signal if the channel is closed. Viceversa the channel will be closed if the thread is interrupted. Only the latter two channels are selectable, i.e. can be used in conjunction with a selector to achieve the effect of the Unix select call.

Here is an example of use of ByteBuffer in conjunction with channels. And here is a trivial use of the MappedByteBuffer concept.

A SelectionKey represents the registration of a selectable channel such as SocketChannel or ServerSocketChannel, with a selector. A selection key corresponds to one of possible operations applicable to a channel, connect, accept, read, and write. That operation is specified when the selectable channel is registered with a selector. For example, if server is a ServerSocketChannel, selector is a Selector, and we want to register with the selector the server to wait on an accept operation, we say
server.register(selector, SelectionKey.OP_ACCEPT);
and if instead we want to register a SocketChannel client to wait on a read operation, we say
client.register(selector, SelectionKey.OP_READ);
Before being registered with a selector the channels should be places in non-blocking mode with
client.configureBlocking(false); and server.configureBlocking(false);

The selector had been created with
Selector selector = Selector.open();
and then, after the channels are registered, it blocks in the select call until a significant event occurs:
selector.select();
When this call returns it means that one or more significant event among those that were registered has occurred. We determine what are the ready channels (i.e. channels where significant events have occurred) with
Set readyKeys = selector.selectedKeys();
Then we iterate thru the ready keys and carry out the corresponding operations

      Iterator iterator = readyKeys.iterator( );
      while (iterator.hasNext( )) {
          SelectionKey key = iterator.next( );
          iterator.remove( );
	  try {
	      if (key.isAcceptable()) {
	          ............
	      } else if (key.isReadable()) {
	          ............
	      } else if (key.isWritable()) {
	          ............
	      } else if (key.isConnectable()) {
	          ............
	      }
 	   } catch (Exception e) {
	      ...........
	   }
Here are three programs that exemplify the use of buffers, channels, selectors, and selectionkeys.
The first is an example from Java I/O, 2nd Edition by Elliote Rusty Harold. It is a concurrent server that accepts connections and creates a thread to write junk to the client until the client disconnects.
The second is a concurrent server from Java NIO by Ron Hitchens that echoes back all the data it receives using a selector to handle the concurrent connections.
The third is also a concurrent server again from Java NIO by Ron Hitchens that combines the use of a selector with a thread pool to handle the request. It answers the question: the selector mechanisms seems ideal for a single processor system since all is done in a single thread. But what about a system with multiple processors? The answer is to let different threads take care of different subsets of the ready selection keys.

Executors and Thread Pools

In last example of the previous section was used the class ThreadPool, defined in the Java API, that represents the concept of a thread pool. And here is code that demonstrates the related concept of ExecutorService with a fixed size thread pool.

Java Remote Method Invocation (RMI)

Java provides an interface above the socket interface for communicating between a client and a server, the Remote Method Invocation facility. Here are pointers to a tutorial and examples