Junto con el almacen de conexiones, hay otras características que podemos diseñar dentro de nuestros programas para mejorar el rendimiento. Este capítulo explica cómo podemos diseñar un applet para que descargue ficheros y recursos de forma más eficiente, o diseñar un programa basado en threads para usar un almacen de threads para ahorrarnos el costoso proceso de arrancar threads.
Una forma de almacenar imágenes en un fichero class es usar un esquema de codificación ASCII como X-PixMap (XPM). De esta forma, en vez de mantener la imágenes en ficheros GIF en el servidor, los ficheros son codificados como un Strings y son almacenados en un sólo fichero class.
Este código de ejemplo usa páquetes del ganador de la JavaCup del JavaOne 1996, que contenía las clases XImageSource y XpmParser. Estas clases proporciona todo los necesario para leer un fichero XPM. Podemos ver esto ficheros en SunSite.
Para el proceso inicial de codificación, hay un número de herramientas gráficas que podemos usar para crear fichero XPM. En Solaris podemos usar ImageTool o una variedad de otros GNU image packages. Podemos ir a la web site Download.com para obtener software de codificación para las plataformas Windows.
El siguiente código extraido del ejemplo de código MyApplet que carga imágenes. Podemos ver el String codificado en la definición XPM de imágenes.
La clase Toolkit crea un objeto Image para cada imagen desde el objeto fuente XPM Image.
La alternativa técnica de abajo usa ficheros GIF. Requiere una petición al servidor para cada imagen cargada.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));
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");
Esta técnica reduce el trafico de la red porque todas las imágenes están disponibles en un sólo fichero class.
Una vez cargado, podemos usar las imágenes para crear botones u otros componentes del interface de usuario. El siguiente segmento de código muestra cómo usar la imágenes con la clase javax.swing.JButton.
ImageIcon icon = new ImageIcon (
kit.createImage (
new XImageSource (_reply)));
JButton button = new JButton (icon, "Reply");
El capítulo: Desarrollar Nuestra Aplicación tiene información sobre cómo crear y firmar ficheros JAR.
El código HTML de abajo usa la etiqueta CODE para especificar el ejecutable del applet MyApplet, y la etiqueta ARCHIVE especifica el fichero JAR que contiene todos los ficheros relacionados con MyApplet. El ejecutable especificado por la etiqueta CODE algunas veces es llamado code base.
Por razones de seguridas los ficheros JAR listados por el parámetro archive deben estar en el mismo directorio o subdirectorio que el codebase del applet. Si no se suministra el parámetro codebase el directorio de donde se cargó el applet se usa como el codebase.
El siguiente ejemplo especifica jarfile como el fichero JAR que contiene todos los ficheros relacionados para el ejecutable MyApplet.class.
<APPLET CODE="MyApplet.class" ARCHIVE="jarfile" WIDTH="100" HEIGHT="200"> </APPLET>
Si la descarga del applet usa múltiples ficheros JAR como se muestra en el siguiente segmento HTML, el ClassLoader carga cada fichero JAR cuando el applet arranca. Por eso, si nuestro applet usa algunos ficheros de recursos de forma infrecuente, el fichero JAR que contiene esos ficheros es descargado sin importar si los recursos van a ser usados durante la sesión o no.
<APPLET CODE="MyApplet.class" ARCHIVE="jarfile1, jarfile2" WIDTH="100" HEIGHT="200"> </APPLET>
Para mejorar el rendimiento cuando se descargan fichero no usados de forma frecuente, ponemos los ficheros usados más frecuentemente dentro de un fichero JAR y los ficheros menos usados en el directorio de la clase del applet. Los ficheros usados poco frecuentemente son localizados y descargados sólo cuando el navegador los necesita.
Este código de ejemplo tomado de la clase Pool.java muestra una forma de implementar la fusión de threads, En el constructor de la fusión (mostrado abajo), se inicializan y arrancan los WorkerThreads. La llamada al método start ejecuta el método run del WorkerThread, y la llamada a wait suspende el Thread mientras el Thread espera a que llegue un trabajo. La última línea del constructor empuja el Thread durmiente hacia la pila.
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);
}
}
Junto al método run, la clase WorkerThread tiene un método wake. Cuando viene el trabajo, se llama al método wake, que asigna los datos y notifica al WorkerThread durmiente (el inicializado por el Pool) para recuperar la ejecución. El método wake llama a notify hace que el WorkerThread bloqueado salga del estado de espera, y se ejecuta el método run de la clase
HttpServerWorker. Una vez realizado el trabajo, el WorkerThread se pone de nuevo en el Stack (asumiento que el Pool de threads no está lleno) o termina.
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));
}
}
En este alto nivel, el trabajo entrante es manejado por el método performWork en la clase Pool. Cuando viene el trabajo, se saca de la pila un WorkerThread existente (o se crea uno nuevo si el Pool está vacío). El WorkerThread durmiente es activado mendiate una llamada a su método wake.
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);
}
El constructor de la clase HttpServer.java crea un nuevo ejemplar Pool para servir ejemplares de la clase
HttpServerWorker. Los ejemplares HttpServerWorker se crean y almacenan como parte de los datos WorkerThread. Cuando se activa un WorkerThread mediante una llamada a su método wake, el ejemplar HttpServerWorker es invocado mediante su método run.
try{
_pool = new Pool (poolSize,
HttpServerWorker.class);
}catch (Exception e){
e.printStackTrace();
throw new InternalError (e.getMessage());
}
Este código está en el método run de la clase
HttpServer.java. Cada vec que viene una petición, el dato es inicializado y el Thread empieza el trabajo.
Nota: Si creamos un nuevo Hashtable por cada WorkerThread provocamos demasiada sobrecarga, sólo modificamos el código para que no use la abstración Worker.
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();
}
El almacen de threads es una técnica efectiva de ajuste de rendimiento que coloca el caro proceso de arranque de threads en la arrancada de la aplicación. De esta forma, el impacto negativo en el rendimiento ocurre sólo una vez durante el arrancada del programa donde se nota menos.