Bandwidth restrictions imposed on networks around the world make network-based
operations potential bottlenecks that can have a significant impact on an
application's performance. Many network-based applications are designed to
use connection pools so they can reuse existing network connections and save
on the time and overhead invested in opening and closing network connections.
Besides connection pooling, there are other features you can design into your
programs to improve performance. This chapter explains how you can design an
applet to download files and resources more efficiently, or design a
thread-based program to use thread pooling to save on the expensive thread
startup process.
Improving Applet Download Speed
Applet download performance refers to the time it takes for the browser to
download all the files and resources it needs to start the applet. An important
factor affecting any applet's download performance is the number of times it
has to request data from the server. You can reduce the number of requests by
packaging the applet images into one class file, or using JavaTM ARchive (JAR)
files.
Packaging Images into One Class
Normally, if an applet has six image buttons, that translates to six additional
requests sent back to the web server to load those image files. Six additional
requests might not seem like much on an internal network, but given connections
of lesser speed and reliability, those additional requests can have a
significant negative impact on performance. So, your ultimate goal should be
to load the applet as quickly as possible.
One way to store images in a class file is to use an ASCII encoding scheme
such as
X-PixMap (XPM).
This way, rather than maintaining the images as GIF files on the server, the
files are encoded as Strings and stored in a single class file.
This code sample uses packages from the JavaCup winner at JavaOne 1996, which
contains the XImageSource and XpmParser classes.
These classes provide all you need to read a standard XPM file.
You can see these files at
SunSite.
For the initial encoding process, there are a number of graphics tools
you can use to create XPM files. On Solaris you can use
ImageTool or a variety of other
GNU image
packages. Go to the Download.com
web site to get the encoding software for Windows platforms.
The following code excerpted from the
MyApplet sample class loads the images.
You can see the coded String form for the images in the
XPM definition of the images.
The Toolkit class creates an Image object for each
image from the XPM Image Source object.
Toolkit kit = Toolkit.getDefaultToolkit();
Image image;
image = kit.createImage (new XImageSource (_reply));
image = kit.createImage (new XImageSource (_post));
image = kit.createImage (new XImageSource (_reload));
image = kit.createImage (new XImageSource (_catchup));
image = kit.createImage (new XImageSource (_back10));
image = kit.createImage (new XImageSource (_reset));
image = kit.createImage (new XImageSource (_faq));
The alternative technique below uses GIF files. It requires a request back
to the web server for each image loaded.
Image image;
image = getImage ("reply.gif");
image = getImage ("post.gif");
image = getImage ("reload.gif");
image = getImage ("catchup.gif");
image = getImage ("back10.gif");
image = getImage ("reset.gif");
image = getImage ("faq.gif");
This technique reduces network traffic because all images are available in
a single class file.
- Using XPM encoded images makes the class size larger,
but the number of network requests fewer.
- Making the XPM image definitions part of your applet class
file, makes the image loading process part of the regular loading
of the applet class file with no extra classes.
Once loaded, you can use the images to create buttons or other user
interface components. This next code segment shows how to use the images
with the javax.swing.JButton class.
ImageIcon icon = new ImageIcon (
kit.createImage (
new XImageSource (_reply)));
JButton button = new JButton (icon, "Reply");
Using JAR Files
When an applet consists of more than one file, you can improve download
performance with Java ARchive (JAR) files. A JAR file contains all of an
applet's related files in one single file for a faster download. Much of
the time saved comes from reducing the number of HTTP connections the browser
must make.
Chapter 9: Deploying Your Application has information on creating and signing
JAR files.
The HTML code below uses the CODE tag to specify the executable
for the MyApplet applet, and the ARCHIVE tag to
specify the JAR file that contains all of MyApplet's related
files. The executable specified by the CODE tag is sometimes
called the code base .
For security reasons the JAR files listed by the archive
parameter must be in the same directory or a sub-directory as the
applets codebase . If no codebase parameter
is supplied the directory from where the applet was loaded is used as
the codebase .
The following example specifies jarfile as the JAR file that
contains the related files for the MyApplet.class executable.
<APPLET CODE="MyApplet.class" ARCHIVE="jarfile"
WIDTH="100" HEIGHT="200">
</APPLET>
If the applet download uses multiple JAR files as shown in the next HTML
segment, the ClassLoader loads each JAR file when the applet
starts. So, if your applet uses some resource files infrequently, the JAR
file containing those infrequently used files is downloaded, regardless of
whether the resources are actually used during that session or not.
<APPLET CODE="MyApplet.class" ARCHIVE="jarfile1, jarfile2"
WIDTH="100" HEIGHT="200">
</APPLET>
To improve performance when an applet has infrequently used files, put the
frequently used files into the JAR file and the infrequently used files into
the applet class directory. Infrequently used files are then located and
downloaded by the browser only when needed.
Thread Pooling
The Java Developer ConnectionSM
(JDC) applet servers and the Java Web ServerTM
make extensive use of thread pooling to improve performance. Thread pooling
is creating a ready supply of sleeping threads at the beginning of execution.
Because the thread startup process is expensive in terms of system resources,
thread pooling makes the startup process a little slower, but improves runtime
performance because sleeping (or suspended) threads are awakened only when
they are needed to perform new tasks.
This code sample taken from the Pool.java
class shows one way to implement thread pooling. In the pool's constructor
(shown below), the WorkerThreads are initialized and started.
The call to the start method executes the run method
of the WorkerThread , and the call to wait in the
run method suspends the Thread while the
Thread waits for work to arrive. The last line of the constructor
pushes the sleeping Thread onto the stack.
public Pool (int max, Class workerClass)
throws Exception {
_max = max;
_waiting = new Stack();
_workerClass = workerClass;
Worker worker;
WorkerThread w;
for ( int i = 0; i < _max; i++ ) {
worker = (Worker)_workerClass.newInstance();
w = new WorkerThread ("Worker#"+i, worker);
w.start();
_waiting.push (w);
}
}
Besides the run method, the WorkerThread class has
a wake method. When work comes in, the wake method
is called, which assigns the data and notifies the sleeping
WorkerThread (the one initialized by the Pool )
to resume running. The wake method's call to notify
causes the blocked WorkerThread to fall out of its wait state,
and the run method of the
HttpServerWorker class is executed.
Once the work is done, the WorkerThread is either put back onto
the Stack (assuming the Thread Pool is not full) or
terminates.
synchronized void wake (Object data) {
_data = data;
notify();
}
synchronized public void run(){
boolean stop = false;
while (!stop){
if ( _data == null ){
try{
wait();
}catch (InterruptedException e){
e.printStackTrace();
continue;
}
}
if ( _data != null ){
_worker.run(_data);
}
_data = null;
stop = !(_push (this));
}
}
At its highest level, incoming work is handled by the performWork
method in the Pool class (shown below). As work comes in, an
existing WorkerThread is popped off of the Stack
(or a new one is created if the Pool is empty). The sleeping
WorkerThread is then activated by a call to its wake
method.
public void performWork (Object data)
throws InstantiationException{
WorkerThread w = null;
synchronized (_waiting){
if ( _waiting.empty() ){
try{
w = new WorkerThread ("additional worker",
(Worker)_workerClass.newInstance());
w.start();
}catch (Exception e){
throw new InstantiationException (
"Problem creating
instance of Worker.class: "
+ e.getMessage());
}
}else{
w = (WorkerThread)_waiting.pop();
}
}
w.wake (data);
}
The HttpServer.java class constructor
creates a new Pool instance to service
HttpServerWorker instances.
HttpServerWorker instances are created and stored as part of the
WorkerThread data. When a WorkerThread is activated
by a call to its wake method, the HttpServerWorker
instance is invoked by way of its run method.
try{
_pool = new Pool (poolSize,
HttpServerWorker.class);
}catch (Exception e){
e.printStackTrace();
throw new InternalError (e.getMessage());
}
This next code is in the run method of the
HttpServer.java class. Every time a
request comes in, the data is initialized and the Thread starts
work.
Note: If creating a new Hashtable for
each WorkerThread presents too much overhead, just modify
the code so it does not use the Worker abstraction.
try{
Socket s = _serverSocket.accept();
Hashtable data = new Hashtable();
data.put ("Socket", s);
data.put ("HttpServer", this);
_pool.performWork (data);
}catch (Exception e){
e.printStackTrace();
}
Thread pooling is an effective performance-tuning technique that puts the
expensive thread startup process at the startup of an application. This way,
the negative impact on performance occurs once at program startup where it is
least likely to be noticed.
[TOP]
|