Anda di halaman 1dari 132

e-Science e-Business

e-Government and their


Technologies
Bryan Carpenter,
Geoffrey Fox, Marlon Pierce
Pervasive Technology Laboratories
Advanced
Java
Indiana
University Bloomington
IN 47404
January 12 2004
dbcarpen@indiana.edu
gcf@indiana.edu
mpierce@cs.indiana.edu
http://www.grid2004.org/spring2004

What are we doing

This is a semester-long course on Grids (viewed as technologies


and infrastructure) and the application mainly to science but
also to business and government
We will assume a basic knowledge of the Java language and then
interweave 6 topic areas first four cover technologies that will
be used by students
1) Advanced Java: including networking, Java Server Pages and
perhaps servlets
2) XML: Specification, Tools, Linkage to Java
3) Web Services: Basic Ideas, WSDL, Axis and Tomcat
4)Grid Systems: GT3/Cogkit, Gateway, XSOAP, Portlet
5) Advanced Technology Discussions: CORBA as istory, OGSADAI, security, Semantic Grid, Workflow
6) Applications: Bioinformatics, Particle Physics, Engineering,
Crises, Computing-on-demand Grid, Earth Science

Course Topic 1

Advanced Java Programming


We will assume basic Java programming proficiency
We will cover Java client/server, three-tiered and network
programming.
Ancillary but interesting Java topics to be covered include
Apache Ant, XML-Beans, and Java Message Service

Material in the last bullet will mostly be introduced in


later sections, as the course unfolds.
First lecture of the segment starts with a fairly
discursive review of Java features.

Reading Material

No particular text for this section, but some material


will come from earlier related courses:
Java HPC Course, September 2003
http://www.hpjava.org/courses/arl
Opennet Technologies Online Course, Fall 2001
http://aspen.ucs.indiana.edu/ptliu
Applications of Information Technology I and II, Spring 2001
http://aspen.ucs.indiana.edu/it1spring01
http://aspen.ucs.indiana.edu/it2spring01

Java History

The Java language grabbed public attention in 1995,


with the release of the HotJava experimental Web
browser, and the subsequent incorporation of Java
into the Netscape browser.
Java had originally been developedunder the name of Oak
as an operating environment for PDAs, a few years before.

Very suddenly, Java became one of the most important


programming languages in the industry.
The trend continued. Although Web applets are less
important today than they were originally, Java was rapidly
adopted by many other sectors of the programming
community.

The Java Virtual Machine

Java programs are not compiled to machine code in the


same way as conventional programming language.
To support safe execution of compiled code on multiple
platforms (portability, security), they are compiled to
instructions for an abstract machine called the Java
Virtual Machine (JVM).
The JVM is a specification originally published by Sun
Microsystems.
JVM instructions are called Java byte codes. They are stored
in a class file.
This execution model is part of the specification of the Java
platform. There are a few compilers from the Java language
to machine code, but it is hard to get these recognized as
Java compliant.

JVM and Performance

The first implementations of the JVM simply


interpreted the byte codes. These implementations were
very slow.
This led to a common misconception that Java is an
interpreted language and inherently slow.

Modern JVMs normally perform some form of


compilation from byte codes to machine code on the fly,
as the Java program is executed.

Run-time Compilation

In one form of Just-In-Time compilation, methods may be


compiled to machine code immediately before they are executed
for the first time. Then subsequent calls to the method just
involve jumping into the machine code.
More sophisticated forms of adaptive compilation (like in the Sun
Hotspot JVMs) initially run methods in interpreted mode,
monitor program behavior, and only spend time compiling
portions of the byte code where the program spends significant
time. This allows more intelligent allocation of CPU time to
compilation and optimization.
Modern JVMs (like the Hotspot server JVM) implement many of
the most important kinds of optimization used by the static
compilers of traditional programming languages.
Adaptive compilation may also allow some optimization approaches that
are impractical for static compilers, because they dont have the run-time
information.

Features of the Java


Language

Prerequisites

We assume you know either Java or C++ moderately


well.
But some things, like threaded and network programming
with Java, will be covered from an introductory level later on.

In this section I will only point out some features and


terminologies that are characteristic of Java and that
you probably should understand.
And highlight some of the differences from C++.

What Java Isnt

C++, mainlynow hard to think of languages as closely related.


Similar syntax for expressions, control constructs, etc, but these are perhaps the
least characteristic features of C++ or Java.

In C++ use features like operator overloading, copy constructors,


templates, etc, to create little languages through class libraries.
Worry about memory management and efficient creation of objects.
Worry about inline versus virtual methods, pointers versus references,
minimizing overheads.

In Java most of these things go away.


Minimal control over memory management, due to automatic garbage
collection.
Highly dynamic : all code is loaded dynamically on demand; implicit run-time
descriptors play an important role, through run-time type checks, instanceof, etc.
Logically all methods are virtual; overloading and implementation of interfaces
is ubiquitous.
Exceptions, rarely used in C++, are used universally in Java.

Java Class Structure

All methods and (non-local) variables are


explicitly member of classes (or interfaces).
No default, global, namespace (except for the names of
classes and interfaces).

Java discards multiple inheritance at the class


level. Inheritance relations between classes are
strictly tree-like.
Every class inheritance diagram has the universal base
class Object at its root.

Java Class Structure (2)

Java introduces the important idea of an interface,


which is logically different from a class.
Interfaces contain no implementation code for the
methods they define.
Multiple inheritance of interfaces is allowed, and this is
one way Java manages without it at the class level.

Since Java 1.2, classes and interfaces can be


nested.
This is a big change to the language: read JLS 2nd
Edition in detail if you dont believe this!

Classes and Instances

Will consistently use the following terminologies


(which are correct):
A class is a type, e.g.
public class A {int x ; void foo() {x = 23 ;}}
An interface is a type, e.g .
public interface B {void goo() ;}
An instance is an object. An object is always an
instance of one particular class.
That class may extend other classes, and implement
multiple interfaces.

Pointers in Java?

Any expression in Java that has class type (or


interface type) is a reference to some instance (or
it is a null reference). E.g. a variable declared:
Aa;

holds a reference to an instance. The objects


themselves are behind the scenes in Java: we
can only manipulate pointers (references) to them.
E.g. a = b ; Only copies a reference, not an object.

But important to note references to objects and


arrays are the only kinds of pointer in Java. E.g.
there are no pointers to fields or array elements or
local variables.

Instance and static members

The following terminologies are common. In:


public class A {
int x
void foo() {}
static int y ;
static void goo() {}
}

We say:
x is an or instance variable, or non-static field.
foo() is an instance method, or non-static method.
y is a static field, or class variable.
goo() is a static method, or class method.

Class Loading

A Java program is typically written as a class with a public, static,


void, main() method, as follows
public class MyProgram {
public static void main(String [] args) {
body of program
}
}

and started by a command like:


$ java MyProgram

This command creates a Java Virtual Machine, loads the class


MyProgram into the JVM, then invoke its main() method.
As this process unfolds, dependencies on other class and interfaces
and their supertypes will be encountered, e.g. through statements
that use other classes. The class loader brings in the class files for
these types on demand. Code is loaded, and methods linked,
incrementally, throughout execution.

The CLASSPATH

Many people have problems getting the


CLASSPATH environment variable right.
Because all linking is done at run-time, must ensure that
this environment variable has the right class files on it.

The class path is a colon-separated (semicolonseparated in Windows) list of directories and jar
files.
If the class path is empty, it is equivalent to .. But if
the class path is not empty, . is not included by default.
A directory entry means a root directory in which class
files or package directories are stored; a jar entry means
a jar archive in which class files or package directories
are stored.

Binary Compatibility

There is a useful property called binarycompatibility between classes. This means that
(within some specified limits) two class files that
implement the same public interface can be used
interchangeably.
It also means that if you pick up an inappropriate
implementation of a given class from the CLASSPATH
at runtime, things can go wrong in an opaque way.

Java Native Interface

Some methods in a class may be declared as native


methods, e.g.:
class B {
public native long add(int [] nums) ;
}

Notice the method add() has the modifier native, and the
body of the method declaration is missing
It is replaced by a semicolonsimilar to abstract methods in
interfaces, etc. But in this case the method isnt abstract.
The implementation of a native method will be given in another
language, typically C or C++ (we consider C).

Implementing native methods is quite involved.


Arguably a good thingit discourages casual use! Generally
need a good reason for resorting to JNI.

A Definition of Java_B_add()
JNIEXPORT jlong JNICALL Java_B_add(JNIEnv * env,
jobject this, jintArray nums) {
jint *cnums ;
int i, n ;
jlong sum = 0 ;
n = (*env)->GetArrayLen(env, nums) ;
cnums = (*env)->GetIntArrayElements(env, nums, NULL) ;

for(i = 0 ; i < n ; i++)


sum += cnums [i] ;
return sum ;

The Invocation API

JNI also provides a very powerful mechanism for going


the other waycalling from a C program into Java.
First the C program needs to create a JVM (initialize all
the data structures associated with a running JVM),
which it does with a suitable library call.
The standard java command works exactly this wayit
uses the JNI invocation API to create a JVM, and call
the main() method of the class specified on the
command line.

The Rest of this Segment

1.

Will cover three core topics in advanced Java:


Multithreaded Programming in Java

2.

Network Programming in Java

3.

Traditional Java class libraries for sockets, URLs.


Overview of Java New I/O.

Java Servlets and Java Server Pages.

Java as a multithreaded language; Java thread synchronization


primitives.

Java technologies for Web Applications.

Other Java techniques (e.g. Java for XML, Web Services) will be
introduced as the course unfolds.

1) Multithreaded
Programming in Java

24

Need for Concurrent Programming

This course is mostly about distributed programming.


This is a different discipline from concurrent or multithreaded
programming, but doing distributed programming without
understanding concurrent programming is error prone.
Some frameworks (e.g. EJB) try to enable distributed
programming while insulating the programmer from the
difficulties of concurrent programming, but eventually you
are likely to hit concurrency issues.
+ Non-determinism
Sequential
programming

+ Partial failures

Concurrent
programming

Distributed
programming

Java as a Threaded Language

In C, C++, etc it is possible to do multithreaded


programming, given a suitable library.
e.g. the pthreads library.

Unlike other languages, Java integrates threads


into the basic language specification in a much
tighter way.
Every Java Virtual Machine must support threads.

Features of Java Threads

Java provides a set of synchronization primitives


based on monitor and condition variable paradigm of
C.A.R. Hoare.
Underlying functionality similar to e.g. POSIX threads.

Syntactic extension for threads (deceptively?) small:

synchronized attribute on methods.


synchronized statement.
volatile keyword.
Other thread management and synchronization captured
in the Thread class and related classes.

But the presence of threads has a wide-ranging effect


on language specification and JVM implementation.

Contents of this Lecture

Introduction to Java Threads.


Mutual Exclusion.
Synchronization between Java Threads using wait() and
notify().
Other features of Java Threads.

Suggested Exercises

Java Thread Basics

29

Threads of Execution

Every statement in a Java program is executed in a


context called its thread of execution.
When you start a Java program in the normal way, the
main() methodand any methods called from that
methodare executed in a singled out (but otherwise
ordinary) thread sometimes called the main thread.
Other threads can run concurrently with the main
thread. These threads share access to the same classes
and objects as the main thread, but they execute
asynchronously, in their own time.
The main thread can create new threads; these threads
can create further threads, etc.

Creating New Threads

Any Java thread of execution (including the main


thread) is associated with an instance of the Thread
class. Before starting a new thread, you must create
a new instance of this class.

The Java Thread class implements the interface


Runnable. So every Thread instance has a method:
public void run() { . . . }

When the thread is started, the code executed in the


new thread is the body of the run() method.
Generally speaking the new thread ends when this
method returns.

Making Thread Instances

There are two ways to create a thread instance (and define the
thread run() method). Choose at your convenience:
1. Extend the Thread class and override the run() method, e.g.:
class MyThread extends Thread {
public void run() {
System.out.println(Hello from another thread) ;
}

}
...
Thread thread = new MyThread() ;

2. Create a separate Runnable object and pass to the Thread constructor:


class MyRunnable implements Runnable {
public void run() {
System.out.println(Hello from another thread) ;
}

}
...
Thread thread = new MyThread(new MyRunnable()) ;

Starting a Thread

Creating the Thread instance does not in itself start the


thread running.
To do that you must call the start() method on the new
instance:
thread.start() ;

This operation causes the run() method to start


executing concurrently with the original thread.
In our example the new thread will print the message
Hello from another thread to standard output, then
immediately terminate.
You can only call the start() method once on any Thread
instance. Trying to restart a thread causes an
exception to be thrown.

Example: Multiple Threads


class MyThread extends Thread {
MyThread(int id) {
this.id = id ;
}
public void run() {
System.out.println(Hello from thread + id) ;
}
private int id ;

}
...
Thread [] threads = new Thread [p] ;
for(int i = 0 ; i < p ; i++)
threads [i] = new MyThread(i) ;
for(int i = 0 ; i < p ; i++)
threads [i].start() ;

Remarks

This is one way of creating and starting p new threads to


run concurrently.

The output might be something like (for p = 4):


Hello from thread 3
Hello from thread 4
Hello from thread 2
Hello from thread 1

Of course there is no guarantee of order (or atomicity) of


outputs, because the threads are concurrent.

One might worry about the efficiency of this approach


for large numbers of threads (massive parallelism).

JVM Termination and Daemon Threads

When a Java application is started, the main() method of the


application is executed in the main thread.
If the main method never creates any new threadsthe
JVM keeps running until the main() method completes (and
the main thread terminates).

Typically, the java command finishes.

If main() creates new threads, by default the JVM


terminates when all user-created threads have terminated.
More generally there are system threads executing in the
background (e.g. threads might be associated with garbage
collection). These are marked as daemon threadsmeaning
that they dont have the property of keeping the JVM
alive. So actually the JVM terminates when all nondaemon threads terminate.

Ordinary user threads can create daemon threads by applying the


setDaemon() method to the thread instance before starting it.

Mutual Exclusion

37

Avoiding Interference

In any non-trivial multithreaded (or shared-memory-parallel)


program, interference between threads is an issue.

Generally interference (or a race condition) occurs if two


threads are trying to do operations on the same variables at the
same time. This often results in corrupt data.

But not always. It depends on the exact interleaving of instructions. This


non-determinism is the worst feature of race conditions.

A popular solution is to provide some kind of lock primitive.


Only one thread can acquire a particular lock at any particular
time. The concurrent program can be written so that
operations on some given variables are only performed by
threads holding the lock for those variables.

In POSIX threads, for example, the lock objects are called mutexes.

Monitors

Java adopts a version of monitors, proposed by C.A.R. Hoare.

Every Java object is created with its own lock (and every lock is
associated with an objectthere is no way to create an isolated
mutex). In Java this lock is often called the monitor lock.

Methods of a class can be declared to be synchronized.

The objects lock is acquired on entry to a synchronized method,


and released on exit from the method.

Synchronized static methods need slightly different treatment.

If methods generally modify the fields (instance variables) of the


method instance, this leads to a natural and systematic association
between locks and the variables they guard.

The critical region is the body of the synchronized method.

Example use of Synchronized Methods


Thread A

Thread B

call to counter.increment()
// body of synchronized method
tmp1 = count ;
count = tmp1 + 1 ;
counter.increment() returns

call to counter.decrement()

Blocked
// body of synchronized method
tmp2 = count ;
count = tmp2 - 1 ;
counter.decrement() returns

Caveats

This approach helps to encourage good practices, and make


multithreaded Java programs less error-prone than, say,
multithreaded C programs.
But it isnt magicit still depends on correct identification
of the critical regions, to avoid race conditions.
Concurrent programming is hard, and if you start with the
assumption Java somehow makes concurrent programming
easy, you are probably going to write some broken
programs!

Example: A Simple Queue


public class SimpleQueue {
public synchronized void add(Object data) {
if (front != null) {
back.next = new Node(data) ;
back = back.next ;
}
else {
front = new Node(data) ;
back = front ;
}
}
public synchronized Object rem() {
Object result = null ;
if (front != null) {
result = front.data ;
front = front.next ;
}
return result ;
}
}

private Node front, back ;

Remarks

This queue is implemented as a linked list with a front


pointer and a back pointer.
The method add() adds a node to the back of the list; the
method rem() removes a node from the front of the list.
The rem() method immediately returns null when the queue is
empty.

The Node class just has a data field (type Object) and a
next field (type Node).
The following slide gives an example of what could go
wrong without mutual exclusion. It assumes two
threads concurrently add nodes to the queue.
In the initial state, Z is the last item in the queue. In the final
state, the X node is orphaned, and the back pointer is null.

The Need for Synchronized Methods


Thread A: add(X)

null

back

back.next = new Node(X) ;


X

Thread B: add(Y)

null

back.next = new Node(Y) ;


X

back

null
null

back

back = back.next ;
X
Z

null
back = back.next ;

null

back

X
Z

back

null

null
null

Corrupt data structure!

The synchronized construct

The keyword synchronized also appears in the


synchronized statement, which has syntax like:
synchronized (object) {
critical region
}
Here object is a reference to any object. The synchronized
statement first acquires the lock on this object, then
executes the critical region, then releases the lock.
Typically you might use this for the lock object, somewhere
inside a non-synchronized method, when the critical region
is smaller than the whole method body.
In general, though, the synchronized statement allows you
to use the lock in any object to guard any code.

Deadlock

Deadlock occurs when a group of threads are mutually waiting


for one another in such a way that none can proceed.
This happens if there is a cycle of waits-for dependencies, e.g.
A waits for B, B waits for C, , D waits for A.

There are unfortunately many ways this can occur. One


common situation is if two threads try to acquire the same pair
of locks in different orders, e.g.:
Thread A
synchronized(x) {

synchronized(y) {

}
}

Thread B
synchronized(y) {

synchronized(x) {

}
}

Performance Cost of synchronized

Acquiring locks introduces an overhead in execution


of synchronized methods. See, for example:
Performance Limitations of the Java Core Libraries,
Allan Heydon and Marc Najork (Compaq),
Proceedings of ACM 1999 Java Grande Conference.

Many of the original utility classes in the Java


platform (e.g. Vector, etc) were specified to have
synchronized methods, to make them safe for the
multithreaded environment.
This was probably a mistake: newer replacement
classes (e.g. ArrayList) dont have synchronized
methodsthe programmer provides synchronization
as needed, e.g. through wrapper classes.

General Synchronization

48

Beyond Mutual Exclusion

The mutual exclusion provided by synchronized


methods and statements is an important category of
synchronization.
But there are other interesting forms of synchronization
between threads. Mutual exclusion by itself is not
enough to implement these more general sorts of thread
interaction (not efficiently, anyway).
POSIX threads, for example, provides a second kind of
synchronization object called a condition variable to
implement more general inter-thread synchronization.
In Java, condition variables (like locks) are implicit in
the definition of objects: every object effectively has a
single condition variable associated with it.

A Motivating Example

Consider the simple queue from the previous example.


If we try to remove an item from the front of the queue
when the queue is empty, SimpleQueue was specified to
just return null.
This is reasonable if our queue is just meant as a data
structure buried somewhere in an algorithm. But what
if the queue is a message buffer in a communication
system?
In that case, if the queue is empty, it may be more
natural for the remove operation to block until some
other thread added a message to the queue.

Busy Waiting

One approach would be to add a method that polls the queue


until data is ready:
public synchronized Object get() {

while(true) {
Object result = rem() ;
if (result != null)
return result ;
}

This works, but it may be inefficient to keep doing the basic


rem() operation in a tight loop, if these machine cycles could be
used by other threads.
This isnt clear cut: sometimes busy waiting is the most efficient solution.

Another possibility is to put a sleep() operation in the loop, to


deschedule the thread for some fixed interval between polling
operations. But then we lose responsiveness.

wait() and notify()

In general a more elegant approach is to use the


wait() and notify() families of methods. These are
defined in the Java Object class.
Typically a call to a wait() method puts the calling
thread to sleep until another thread wakes it up
again by calling a notify() method.

We will speak of wait() putting a thread to sleep inside a


particular object, meaning we use the condition variable
associated with that object. The notify() call that
subsequently wakes the thread must be called on the
same object.

wait() and notify() II

In our example, if the queue is currently empty, the


get() method would invoke wait(). This causes the
get() operation to block.
Later when another thread calls add(), putting data
on the queue, the add() method invokes notify() to
wake up any sleeping thread. The original get()
call can then return.

A Simplified Example
public class Semaphore {
int s ;
public Semaphore(int s) { this.s = s ; }
public synchronized void add() {
s++ ;
notify() ;
}

public synchronized void get() throws InterruptedException {


while(s == 0)
wait() ;
s-- ;
}

Remarks I

Rather than a linked list we have a simple counter,


which is required always to be non-negative.
add() increments the counter.
get() decrements the counter, but if the counter was zero
it blocks until another thread increments the counter.

The data structures are simplified, but the


synchronization features used here are essentially
identical to what would be needed in a blocking
queue (left as an exercise).
Some may recognize this as an implementation of a
classical semaphorean important synchronization
primitive in its own right.

Remarks II

wait() and notify() should be used inside synchronized


methods of the object they are applied to.
More precisely, the calling thread must hold the objects
monitor lock.

The wait() operation pauses the thread that calls it.


It also releases the lock that the thread holds on the
object, for the duration of the wait() call.
The lock must be claimed again, before continuing after
the pause.

While the lock is temporarily released, another


synchronized method can proceed.
This method may wake up the first, by calling notify().

Remarks III

Several threads can wait() simultaneously in the


same object.
If any threads are waiting in the object, the notify()
method wakes up exactly one of those threads. If no
threads are waiting in the object, notify() does nothing.

Common lore has it that one should always put a


wait() call in a loop, in case the condition that
caused the thread to sleep has not been resolved
when the wait() completes.
The logic in the example here doesnt strictly require it
an if would also work.

A wait() method may throw an InterruptedException


(rethrown by get() in the example). This will be
discussed later.

Another Example
public class Barrier {
private int n, generation = 0, count = 0 ;
public Barrier(int n) { this.n = n ; }
public synchronized void synch() throws InterruptedException {
int genNum = generation ;
count++ ;
if(count == n) {
count = 0 ;
generation++ ;
notifyAll() ;
}
else
while(generation == genNum)
wait() ;
}
}

Remarks

This class implements barrier synchronizationan important


operation in shared memory parallel programming.
It synchronizes n processes: when n threads make calls to synch()
the first n-1 block until the last one has entered the barrier.
The method notifyAll() generalizes notify(). It wakes up all
threads currently waiting on this object.
Many authorities consider use of notifyAll() to be safer than notify(),
and recommend always to use notifyAll().

In the example, the generation number labels the current,


collective barrier operation: it is only really needed to control the
while loop round wait().
And this loop is only really needed to conform to the standard pattern of
wait()-usage, mentioned earlier.

Final Remarks on Synchronization

We illustrated with a couple of simple examples that wait()


and notify() allow various interesting patterns of thread
synchronization (or thread communication) to be
implemented.
In some sense these primitives are sufficient to implement
general concurrent programmingany pattern of
thread synchronization can be implemented in terms of
these primitives.
For example you can easily implement message passing between
threads (left as an exercise)

This doesnt mean these are necessarily the last word in


synchronization: e.g. for scalable parallel processing one
would like a primitive barrier operation more efficient
than the O(n) implementation given above.

Other Features of Java


Threads

61

Other Features

This lecture isnt supposed to cover all the detailsfor


those you should look at the spec!
But we mention here a few other features you may find
useful.

Join Operations

The Thread API has a family of join() operations. These


implement another simple but useful form of
synchronization, by which the current thread can
simply wait for another thread to terminate, e.g.:
Thread child = new MyThread() ;
child.start() ;
Do something in current thread
child.join() ;

// wait for child thread to finish

Priority and Name

Thread have properties priority and name, which can be


defined by suitable setter methods, before starting the
thread, and accessed by getter methods.

Sleeping

You can cause a thread to sleep for a fixed interval


using the sleep() methods.
This operation is distinct fromand less powerful than
wait(). It is not possible for another thread to
prematurely wake up a thread that was paused using
sleep().
If you want to sleep for a fixed interval, but allow another
thread to wake you beforehand if necessary, use the variants
of wait() with timeouts instead.

Deprecated Thread Methods

There is a family of methods of the Thread class that was


supposed to give life-or-death control over threads.
Experience showed these didnt really work, and killing
threads is no longer considered acceptable in polite society.
If you need to interrupt a running thread, you should explicitly
write the thread it in such a way that it pays attention to
interrupt conditions (see the next slide) and terminates itself.
If you want to run an arbitrary thread in such a way that it can be
killed and garbage collected by an external agent, you probably need to
fork a separate process, not a thread.

The deprecated methods include stop(), destroy(), suspend(),


and resume().

Interrupting Threads

Calling the method interrupt() on a thread instance


requests cancellation of the thread execution.
This works in an advisory way: the code for the thread must
explicitly test whether it has been interrupted, e.g.:
public void run() {
while(!interrupted())
do something
}
Here interrupted() is a static method of the Thread class.
If the interrupted thread is executing a blocking operation like
wait() or sleep(), the operation will throw an InterruptedException.
Interruptible threads should catch this exception and terminate
themselves.

This mechanism depends on suitable implementation of


the thread body. The programmer must decide at the
outset whether it is important that a particular thread be
responsive to interruptsoften it isnt.

Thread Groups

There is a mechanism for organizing threads into


groups. This may be useful for imposing security
restrictions on which threads can interrupt other
threads, for example.
Check out the API of the ThreadGroup class if you
think this may be important for your application.

Thread-Local Variables

An object from the ThreadLocal class stores an


object which has a different, local value in every
thread.
Check the API of the ThreadLocal class for details.

Volatile Variables

Suppose a the value of a variable must be accessible by multiple


threads, but you decided you cant afford the overheads of
synchronized methods or the synchronized statement.
Presumably effects of race conditions are known to be innocuous.

Java does not guaranteeabsent lock operations that force


write-back to main memorythat the value of a variable written
by a one thread will be visible to other threads.
But if you declare a field to be volatile:
volatile int myVariable ;

the JVM is supposed to synchronize the value of any threadlocal (cached) copy of the variable with central storagemaking
it visible to all threadsevery time the variable is updated.
The exact semantics of volatile variables and the Java memory
model in general is still controversial, see for example:
A New Approach to the Semantics of Multithreaded Java,
Jeremy Manson and William Pugh,
http://www.cs.umd.edu/~pugh/java/memoryModel/

Threads on Symmetric Multiprocessors

Most modern implementations of the Java Virtual


Machine will map Java threads into native threads of
the underlying operating system.
For example these may be POSIX threads.

On multiprocessor architectures with shared


memory, these threads can exploit multiple available
processors.
Hence it is possible to do true parallel programming
using Java threads within a single JVM.
See the lectures on Java HPC, cited earlier, for
examples.

2) Network
Programming in Java

72

Contents of this Section

Basics of network programming in Java

Sockets background
Socket classes, with simple HTTP examples
Internet address classes
URL classes

Overview of New I/O extensions


Efficient data transfer
Non-blocking sockets
Multiplexing (select)

JSSE elements

Sockets, Addresses and


URLs

74

Sockets

Sockets first appeared in BSD UNIX (designed by


Bill Joylater a designer of Java) circa 1982.
Cross-protocol API for networking. Original
implementation supported protocols including:
TCP/IP
Xerox NS
Local UNIX inter-process communication.

Today available in all variants of UNIX/Linux, and


in Windows through the WinSock API.
Directly support a client/server architecture.
Support connection-oriented protocols like TCP,
and connectionless protocols like UDP.

BSD Socket Calls


Network

Client

socket() : create
socket
connect():
write()

: send request

Server

socket(): create socket


bind() : name socket
listen() :
accept(): accept connection
read() : get request
. . . process request . . .
write(): send reply

read()

: get reply

Port Numbers

The bind() call on the server side establishes a


well-known address for the listening socket.
In the case of an TCP/IP socket the important
part of this is the port number.
A port number is an integer between 0 and 64K.
On any given host, only one server socket can be
listening on a particular port at a particular time.
In UNIX, port numbers below 1024 can only be used by
a privileged user (the super-user). Any user can create
a server socket listening on higher ports.
Low port numbers are used by standard services, e.g.:
23 is the default port number for telnet
80 is the default port number for HTTP servers

Making a Connection

The client makes a connect() call, specifying the


remote host IP address, and the port number for the
server socket it wants to connect to.
Meanwhile the server is waiting on an accept() call on the
server socket.

When the connection is established, the accept() call


completes, returning a reference to a new socket.
Data is subsequently exchanged through the socket pair
consisting of the client socket, and the new socket on the
server, returned by the accept() call.

Sockets in Java

Using sockets from C is traditionally quite hard. The


arguments of the BSD socket functions are complex.
Perhaps in part because of the historical need to support
multiple protocols.

Luckily the API has been greatly simplified in the


Java binding for sockets.
The associated classes are in the package java.net.

Java Sockets from the Client Side

A Java program can open a socket connection in one


step using a constructor of the Socket class:
Socket t = new Socket(hostName, port) ;

Here hostName is a string, like grid2004.org, and


port is an integer, like 80.
This constructor subsumes the socket() and connect()
calls in the BSD API.

The Socket class has methods getInputStream() and


getOutputStream(), returning Java stream objects
that swap data between the connected socket pair.
The connection is bi-directional: both client an server can
read and write.

A Simple Client
import java.io.* ;
import java.net.* ;
public class TrivialBrowser {
public static void main(String [] args) throws IOException {
Socket s = new Socket(www.grid2004.org, 80) ;
PrintWriter out = new PrintWriter(
new OutputStreamWriter(s.getOutputStream())) ;
out.print("GET /spring2004/index.html HTTP/1.1\r\n") ;
out.print("Host: www.grid2004.org\r\n\r\n") ;
out.flush() ;

BufferedReader in = new BufferedReader(


new InputStreamReader(s.getInputStream())) ;
String line ;
while((line = in.readLine()) != null)
System.out.println(line) ;

Remarks

This implements a (drastically restricted) Web client.

Cut and paste this slide, compile and run the code. It
prints out the HTML source for the course home page.
It connects to port 80 on the server (the HTTP port).
It gets an output stream to write to the socket using
getOuputStream().
It sends an HTTP GET request on the stream, specifying
the file it1spring01/index.html relative to the servers
document root.
It gets an input stream to read from the socket using
getInputStream().
It copies lines from the socket connection to the console.

Java Sockets from the Server Side

The BSD operations socket(), bind() and listen() for a server-side


socket are subsumed in a constructor for the ServerSocket class:
ServerSocket s = new ServerSocket(port) ;
Here port is the integer port number, such as 80 (if you are
writing a Web server), on which the server will listen.
Next the Java server will call the accept() method and wait for
clients to connect to it. accept() returns an ordinary socket,
completing the socket-pair for the connection:
Socket connection = s.accept() ;
After processing the request, the client goes back to waiting on
accept(), for new client requests.
Real servers typically fork a thread or process to deal with the request,
and return immediately to waiting for the next client connection.

A Simple Server
public static void main(String [] args) throws Exception {
ServerSocket server = new ServerSocket(8080) ;
while(true) {
Socket sock = server.accept() ;
BufferedReader in = new BufferedReader(
new InputStreamReader(sock.getInputStream()) ;
String header = in.readLine() ;
. . . Skip over any other lines in request packet . . .
String fileName = pathcomponentfrom2ndfieldof header ;
DataOutputStream out =
new DataOutputStream(sock.getOutputStream()) ;
if( filefileNameexists ) {
byte [] bytes = contentsoflocalfile fileName ;
out.writeBytes(HTTP/1.0 200 OK\r\n) ;
out.writeBytes(Content-Length: + bytes.length + \r\n) ;
out.writeBytes(Content-Type: text/html\r\n\r\n) ;
out.write(bytes) ;
} else { SendHTTPerrorstatus }
}
}

Remarks

This implements a (drastically restricted) Web server.


It creates a server socket listening to port 8080 on the local
host.
It gets a socket connection from a client using the accept()
method, and then gets the input stream from the socket using
getInputStream().
We handle only GET requests; the second field will
normally be the file name (preceded by /).
It reads the file (assuming . as document root) and writes it
to the output stream of the socket, in HTTP.
A realistic server would probably spawn a new thread to deal
with each transaction. The main loop would return
immediately to waiting on accept().

Other Features of java.net sockets

The Socket and ServerSocket classes provide a bunch


of inquiry methods to determine the socket state.
But there arent too many more operations one can
actually perform on sockets
One notable thing is setting a time out for I/O operations.

Notable things you cant do include I/O in nonblocking mode, and any kind of select functionality.
These important features werent added until J2SE 1.4, in
the java.nio packages.
In unaugmented java.net sockets, the closest you can come is
to execute socket operations in dedicated threads.

Internet Addresses

The class java.net.InetAddress bundles together


various useful functions on Internet address
DNS lookup, reverse name resolution, etc.

Example methods
static InetAddress getByName(String host) {}
static InetAddress getByAddress(byte [] addr) {}
byte [] getAddress {}
String getCanonicalHostName() {}
static InetAddress getLocalHost() {}

InetAddress objectscanbepassedtotheconstructors
ofsocketclasses.

URL Objects

Instead of explicitly opening a socket connection to


a Web server, a client can read information using
the higher level URL class.
A constructor takes a URL string and creates a URL
object:
URL url =
new URL(http://www.grids2004.org/spring2004/) ;
This constructor may throw a MalformedURLException.

This class is mostly (only?) useful for clients.

Reading a File Using a URL Object

Now if url is a URL object, the resource can be read by


opening a stream on the URL:
BufferedReader in =
new BufferedReader(
new InputStreamReader(url.openStream())) ;

This example creates a character stream that can be


read like any other.

URL Connection Objects

A class java.net.URLConnection provides additional


functionality on URLs. A URLConnection is created
by the openConnection() method:
URLConnection connection = url.openConnection() ;

Methods on connection allow to return fields from


the HTTP header:
String getContentType() {}
int getContentLength() {}
...

You can also open an InputStream or OutputStream


on a URL connection. The latter is used for HTTP
POST requests.

UDP in Java

So far discussed use of Java sockets for TCP.


The User Datagram Protocol is an alternative which is
neither connection-oriented nor reliable.
It transports datagrams: messages of fixed (limited) size.
Messages may occasionally be lost; they may also be
delivered out of order.
But for applications that dont need strong guarantees it
can be faster than TCP, e.g. the Internet Domain Naming
Service is implemented over UDP.
Finally, you have to use UDP if you want to exploit IP
multicast.

A UDP Message Producer


import java.net.* ;
public class UDPProducer {
public static void main(String [] args) throws java.io.IOException {
DatagramSocket sock = new DatagramSocket() ;
InetAddress addr = InetAddress.getByName("grids.ucs.indiana.edu") ;
int port = 3516 ;
for(int i = 0 ; i < 10 ; i++) {
String message = "message " + i ;
byte [] data = message.getBytes() ;
DatagramPacket packet =
new DatagramPacket(data, data.length, addr, port) ;

sock.send(packet) ;

A UDP Message Consumer


import java.net.* ;
public class UDPConsumer {
public static void main(String [] args) throws java.io.IOException {
int port = 3516 ;
DatagramSocket sock = new DatagramSocket(port) ;
byte [] buffer = new byte [65536] ;
while(true) {
DatagramPacket packet =
new DatagramPacket(buffer, buffer.length) ;
sock.receive(packet) ;
String message =
new String(packet.getData(), 0, packet.getLength()) ;

System.out.println(message) ;

Java New I/O

94

NIO: New I/O

Prior to the J2SE 1.4 release of Java, I/O had


become a performance bottleneck.
The old java.io stream classes had too many software
layers to be fast.
No way to multiplex data from multiple sources without
incurring thread context switches
No way to exploit modern OS tricks for high
performance I/O, like memory mapped files.

New I/O changed that.

Features of New I/O

New I/O provides:


A hierarchy of dedicated buffer classes that allow data to
be moved from the JVM to the OS with minimal
memory-to-memory copying, and without overheads
like switching byte ordereffectively give Java a
window on system memory.
A unified family of channel classes that allow data to be
fed directly from buffers to files and sockets, without
going through the slow old stream classes.
Non-blocking I/O on sockets.
A family of classes to directly implement selection (or
readiness testing, or multiplexing) over a set of channels.
NIO also provides file locking for the first time in Java.

References

The Java NIO software is part of J2SE 1.4 and later,


from
http://java.sun.com/j2se/1.4

Online documentation is at:


http://java.sun.com/j2se/1.4/nio

There is an authoritative book from OReilly:


Java NIO, Ron Hitchens, 2002

New I/O Buffers

98

Buffers

A Buffer object is a container for a fixed amount of


data.
It behaves something like a byte [] array, but is
encapsulated so that the internal storage may be a
block of system memory.
Adding data to, or getting it from, a buffer can be a very
direct way of getting information between a Java program
and the underlying operating system.

All the I/O operations in New I/O operate on these


buffer objects.

The java.nio.Buffer Hierarchy

Buffer

CharBuffer

IntBuffer

DoubleBuffer

ShortBuffer

LongBuffer

FloatBuffer

ByteBuffer

MappedByt
eBuffer

The ByteBuffer Class

The most important buffer class in practice is the


ByteBuffer class. This represents a fixed-size vector
of primitive bytes.
The storage used internally by the buffer class is
called the backing store.
This backing store can either be an ordinary Java
array, or a block of system memory.
If it is system memory, the buffer is called a direct buffer.
Think of system memory as meaning something like a C
array allocated by malloc(). It is not memory managed by
the JVM, subject to garbage collection, etc.

Creating Buffers

There are various factory methods that can be used to


create a new ByteBuffer, including:
ByteBuffer wrap(byte [] array)
ByteBuffer allocate(int capacity)
ByteBuffer allocateDirect(int capacity)

These are all static methods of the ByteBuffer class:


wrap() creates a ByteBuffer backed by the Java array
provided by the caller.
allocate() creates a ByteBuffer backed by an anonymous Java
array, size capacity.
allocateDirect() creates a direct ByteBuffer, backed by
capacity bytes of system memory.

Examples
import java.nio.* ;
public class CreateBuffers {
public static void main(String [] args) {
int BUF_SIZE = 1024 ;
byte [] myBacking = new byte [BUF_SIZE] ;
ByteBuffer buffer1 = ByteBuffer.wrap(myBacking) ;
// Uses array myBacking for storage.
ByteBuffer buffer2 = ByteBuffer.allocate(BUF_SIZE) ;
// Uses buffer2.array() for storage.
ByteBuffer buffer3 = ByteBuffer.allocateDirect(BUF_SIZE) ;
// Uses inaccessible system memory for storage.
}

ByteBuffer Reads and Writes

Has a family of put() and get() methods for writing and


reading the buffer, e.g.:
byte get()
get(byte [] dst)

// Get the next byte in the buffer


// Get the next block of bytes

put(byte b)
put(byte [] src)

// Write b to the next position in buffer


// Write block starting at next position

I omitted the some of the return types to avoid confusion. These


methods typically return a reference the original possibly modified
buffer.

The put() and get() operations shown above are all relative
operations: they get data from, or insert data into, the
buffer, starting at the current position in the buffer.

Relative Reads and Writes

The position property works like the file pointer in sequential


file access (but dont confuse it with a file pointer!)
The superclass Buffer has methods for explicitly setting the position
and related properties.

There is also a limit property that has a confusing dual role:


If you are reading a buffer, it should be the total amount of data
previously written to the buffer.
If you are writing to a buffer, it should normally be the capacity of the
buffer.

Various operations implicitly work on the data between


position and limit.
There are a also get() and put() methods that access bytes at
absolute locations in the buffer, if you need them.

Example: Writing and Reading


ByteBuffer buffer = ByteBuffer.allocateDirect(BUF_SIZE) ;
byte [] src = hello world.getBytes() ;
buffer.put(src) ;
// Write data to buffer
buffer.flip() ;

// Prepare buffer for draining

byte [] dst = new byte [2048] ;


buffer.get(dst, 0, buffer.limit()) ;

// Read data from buffer

System.out.println(new String(dst, 0, buffer.limit())) ;


buffer.clear() ;

// Empty buffer (optional here).

Remarks

After you finish writing to a buffer the flip() method can


be used to prepare the buffer for reading.
Technically, flip() sets limit to the current value of position, and
then sets position to zero.

You can use the get() variant:

get(byte [] dst, int offset, int length)

to read less than dst.length bytes from the buffer.


Note offset is in the dst array, not the buffer!

You can clear() a buffer if you want to write to it again.

Technically, clear() sets position to zero, and sets limit to buffers


capacity.

Other Primitive Types

You can write other primitive types (char, int, double, etc)
to a ByteBuffer by methods like:
ByteBuffer putChar(char value)
ByteBuffer putInt(int value)

The putChar() method writes of the 2 bytes in a Java char,


the putInt() methods write 4 bytes, etc.
There are corresponding getChar(), getInt(), methods.
Take care: it is possible to write data as one type and read it as
another.

Raises the question of what byte order the bytes of an int


(say) are written in.

Endian-ness

Can map a number (int, double, ) to a sequence of bytes,


with either most significant byte first (big-endian), or least
significant byte first (little-endian).
Big-Endian: Sun Sparc, PowerPC, numeric fields in IP headers,
Little-Endian: Intel processors

In old java.io, numeric types always written big-endian.


I/O bottleneck if processor is little-endian.

In java.nio, the programmer specifies the byte order as a


property of a ByteBuffer, by calling one of:
myBuffer.order(ByteOrder.BIG_ENDIAN)
myBuffer.order(ByteOrder.LITTLE_ENDIAN)
myBuffer.order(ByteOrder.nativeOrder())
Latter ensures numeric data can be copied between buffer and JVM
(which uses processor native order) without reformatting.

View Buffers

ByteBuffer has no methods for bulk transfer of arrays other


than type byte[].
Instead, create a view of (a portion of) a ByteBuffer as any
other kind of typed buffer, then use the bulk transfer
methods on that view. Following methods of ByteBuffer
create views:
CharBuffer asCharBuffer()
IntBuffer asIntBuffer()

To create a view of just a portion of a ByteBuffer, set


position and limit appropriately beforehandthe created
view only covers the region between these.

Channels

111

Channels

A channel is a new abstraction in java.nio.


In the package java.nio.channels.

Channels are like high-level versions of the filedescriptors in UNIX-like operating systems.
So a channel is a handle for performing I/O operations,
etc, on an open file or socket.

Every java.nio channel has a peer java.io object,


one of:

FileInputStream, FileOutputStream,
RandomAccessFile, Socket,
ServerSocket or DatagramSocket.
The traditional Java handle objects are still usedthe
channel just provides extra NIO-specific functionality.

Simplified Channel Hierarchy


<<<Interface>>>
Channel

<<<interface>>>
ByteChannel

FileChannel

DatagramChannel

SelectableChannel

SocketChannel

ServerSocketChannel

Some of the inheritance arcs here are indirect: we missed


out some interesting intervening classes and interfaces.

Opening A Channel

Socket channel classes have static factory methods


called open(). One form takes a
java.io.InetSocketAddress as argument.
File channels are not created directly; first create a
java.io handleone of FileInputStream,
FileOutputStream, or RandomAccessFilethen
use the new getChannel() method to get the peer
channel.

Examples
import java.nio.* ;
import java.nio.* ;
import java.nio.* ;
public class CreateChannels {
public static void main(String [] args) throws IOException {
InetSocketAddress addr =
new InetSocketAddress("www.grid2004.org", 80) ;
SocketChannel sc = SocketChannel.open(addr) ;
// Create a socket channel.
RandomAccessFile raf =
new RandomAccessFile("CreateChannels.class", "r") ;
FileChannel fc = raf.getChannel() ;
// Get a file channel.
}

Using Channels

Any channel that implements the ByteChannel


interface (namely all channels except
ServerSocketChannel) provide a read() and a
write() instance method:
int read(ByteBuffer dst)
int write(ByteBuffer src)
These may look reminiscent of the read() and write()
system calls in UNIX:
int read(int fd, void* buf, int count)
int write(int fd, void* buf, int count)

Example: Sending an HTTP Request


int BUF_SIZE = 1024 ;
ByteBuffer buffer = ByteBuffer.allocateDirect(BUF_SIZE) ;
InetSocketAddress addr =
new InetSocketAddress("www.grid2004.org", 80) ;
SocketChannel sc = SocketChannel.open(addr) ;
String request = "GET /spring2004/index.html HTTP/1.1\r\n" +
"Host: www.grid2004.org\r\n\r\n" ;
buffer.put(request.getBytes()) ;
buffer.flip() ;
sc.write(buffer) ;
buffer.clear() ;

Example (cont.): Dump Response to a File


FileOutputStream fs =
new FileOutputStream(response.txt") ;
FileChannel fc = fs.getChannel() ;
while(sc.read(buffer) != -1) {
buffer.flip() ;
while(buffer.hasRemaining())
// position < limit
fc.write(buffer) ;
buffer.clear() ;
}

Nonblocking Operations

By calling the method

socket.configureBlocking(false) ;

you put a socket into nonblocking mode.


In non-blocking mode:

A read() operation only transfers data that is immediately


available. If none, it returns 0.
If data cannot be immediately written to a socket, a
write() operation will immediately return 0.
For a server socket, if no client is currently trying to
connect, the accept() method immediately returns null.
The connect() method is more complicatednegotiation
with the server is always started. Should then poll
channel until finishConnect() returns true.

Interruptible Operations

The standard channels in NIO are all interruptible.


If a thread is blocked waiting on a channel, and the
threads interrupt() method is called, the channel will
be closed, and the thread will be woken and sent a
ClosedByInterruptException.
To avoid race conditions, the same will happen if an
operation on a channel is attempted by a thread whose
interrupt status is already true.
See the lecture on threads for a discussion of interrupts.

This represents progress over traditional Java I/O,


where interruption of blocking operations was not
guaranteed.

Other Features of Channels

File channels provide a quite general file locking


facility, but we dont have space to discuss it here.
There is a special channel implementation
representing a kind of pipe, which can be used for
inter-thread communication.

Selectors

122

Readiness Selection

Prior to New I/O, Java provided no standard way of


selecting from a set of possible socket operations just the
ones that are currently ready to proceed.
Previously one could achieve similar effects in Java by doing
blocking I/O operations in separate threads, then merging the results
through Java thread synchronization. But this can be inefficient
because thread context switching and synchronization is quite slow.

One way of achieving the desired effect in New I/O would be


set all the channels involved to non-blocking mode, and use
a polling loop to wait until some are ready to proceed
(busy-waiting).
A more structuredand potentially more efficient
approach is to use Selectors.
This corresponds to using the select() system call in many flavors of
UNIX.

Classes Involved in Selection

Selection can be done on any channel extending


SelectableChannelout of the standard channels,
this means the three kinds of socket channel.
The class that supports the select() operation itself is
Selector. This is a sort of container class for the set of
channels in which we are interested.
The last class involved is SelectionKey, which is said
to represent the binding between a channel and a
selector.
In some sense it is part of the internal representation of the
Selector, but the NIO designers decided to make it an
explicit part of the API.

Setting Up Selectors

Create a selector by the open() factory method. This is a static


method of the Selector class.
A channel is added to a selector by calling the method:
SelectionKey register(Selector sel, int ops)
This (slightly oddly) is an instance method of the SelectableChannel class
(rather than an operation on Selector).
Here ops is a bit-set representing the interest set for this channel. Created
by oring together one or more of:
SelectionKey.OP_READ
SelectionKey.OP_WRITE
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
A channel added to a selector must be in nonblocking mode!

The returned SelectionKey created gets stored in the Selector; in


simple cases you dont need to save it yourself.

Example

Here we create a selector, and register three preexisting channels to the selector:
Selector selector = Selector.open() ;
channel1.register (selector, SelectionKey.OP_READ) ;
channel2.register (selector, SelectionKey.OP_WRITE) ;
channel3.register (selector, SelectionKey.OP_READ |
SelectionKey.OP_WRITE) ;

For channel1 the interest set is reads only, for


channel2 it is writes only, for channel3 it is reads
and writes.
Note all channels must be in non-blocking mode.

select() and the Selected Key Set

To inspect the set of channels, to see what operations are


newly ready to proceed, you call the select() method on the
selector.
This call affects a set of selected keys embedded in the selector.

To use selectors, you must understand that a selector


maintains a Set object representing this selected keys set.
Because each key is associated with a channel, this is equivalent to a
set of selected channels.
The set of selected keys is different from (normally a subset of) the
registered key set.
Each time the select() method is called it may add new keys to the
selected key set, as operations become ready to proceed.
You, as the programmer, are responsible for explicitly removing keys
from the selected key set inside the selector, as you deal with
operations that have become ready.

Ready Sets

There is one more complication.


We saw that each key in the registered key set has an
associated interest set, which is a subset of the 4 possible
operations on sockets.
Similarly each key in the selected key set has an associated
ready set, which is a subset of the interest setrepresenting
the actual operations that have been found ready to proceed.
Besides adding new keys to the selected key set, a select()
operation may add new operations to the ready set of a key
already in the selected key set.
To probe the ready set of a SelectionKey you can use :
isReadable()
isWriteable()
isConnectable()
isAcceptable()

A Pattern for Using select()

register some channels with selector


while(true) {
selector.select() ;

Iterator it = selector.selectedKeys().iterator() ;
while( it.hasNext() ) {
SelectionKey key = it.next() ;
if( key.isReadable() )
perform read() operation on key.channel()
if( key.isWriteable() )
perform write() operation on key.channel()
if( key.isConnectable() )
perform connect() operation on key.channel()
if( key.isAcceptable() )
perform accept() operation on key.channel()

it.remove() ;

Remarks

More generally, the code that handles a ready


operation may also alter the set of channels
registered with the selector

e.g. after doing an accept() you may want to register


the returned SocketChannel with the selector, to wait
for read() or write() operations.

In most cases only a subset of the possible


operations read, write, accept, connect are in
interest sets of keys you registered, so you wont
need all 4 tests.

Key Attachments

One problem is that when it.next() returns a key,


there is no convenient way to know which registered
channel it corresponds to.
You can specify an arbitrary object as an attachment
to the key when you create it; later when you get the
key from the selected set, you can extract the
attachment, and use its content to decide which
channel this is and how to treat it.
At its most basic the attachment might just be an Integer
index identifying the channel.

New I/O Conclusion

We briefly visited several topics in New I/O.


New I/O has been widely hailed as an important
step forward in getting serious performance out of
the Java platform.
Besides raw performance, it provides the most
critical I/O and networking functionalities that were
absent in earlier versions of Java.

Anda mungkin juga menyukai