martes, 7 de abril de 2009

Reik

Reik (Biografia)


REIK, graba sus primeros demos, imprimiéndoles el sello que los caracterizaría desde el primer momento, y que fue bien recibido por la audiencia Mexicalense. Posteriormente, en un movimiento que podríamos denominar "underground", REIK se abrió camino en diferentes ciudades de la República Mexicana, "Levemente" y “Ahora sin ti” se posicionaron rápidamente en el gusto de los jóvenes. Su sonido fresco y diferente, tuvo la fortuna de ser aceptado muy bien por el público. Fue a inicios del 2004 cuando se integra Gilberto Marín Espinoza “Bibi”, definiendo asi, el concepto de trío pop. El tema "Yo Quisiera", se colocó muy bien en la radio local y fue detonador de este proyecto.



Jesús (voz principal) nació el 9 de Julio en la ciudad de Mexicali, B.C.

Desde los seis años comenzo a cantar en coros y misas. Sus influencias musicales son muy diversas, John Mayer, Justin Timberlake, Robbie Williams, entre otros. Además, le gusta tocar el piano.

Disfruta mucho la compañia de sus amigos, lo que le permite salir a divertirse, viajar y conocer a gente.



Julio (guitarra acústica), nació el 21 de Diciembre en la ciudad de Mexicali, B.C.

Toca la guitarra desde los 12 años, teniendo como influencia musical a Further Seems Forever.

Le gusta practicar deportes como lo son el basquetboly el futbol americano. Además de que le encanta salir a divertirse, conocer gente nueva y por supuesto tocar la guitarra.

"Bibi" (guitarra eléctrica) como lo conocen todos, nació el 26 de Enero en la ciudad de Mexicali, B.C.

A los 11 años descubrió una

guitarra en el closet de su casa, y desde entonces no la ha dejado en paz. Siempre se entusiasmo con la música y la los 17 años formó una banda de garage. A los 19, su primo Micky Sandoval lo invita a formar parte de un grupo de covers antrero y desde esa invitación, no ha parado de tocar en escenarios de todo tipo.

Fue el último en formar parte de Reik, sus influencias musicales son Incubus, Radiohead, Ceratti, entre otros. Le gusta leer, meditar, enfiestarse y salir con sus amigos.


Reik (Discografia)

(Reik) Tras la firma del contrato con Sony music, a finales del mes de febrero sale a la venta su primer material discográfico, "Reik", producido por el mismo productor del grupo Abelardo Vázquez y por Kiko Cibrian. El disco contiene 11 temas inéditos y su primer corte "Yo Quisiera", se colocó inmediatamente en los primeros puestos de las radios locales y fue el detonador del grupo.
Este es uno de los discos que Reik edito a lo largo de su trayectoria. El album titulado Reik fue editado en el año 2005.





Letras de las canciones Album "Reik"♫♪♫♪



(Secuencia)Reik es un trio mexicano que desde hace muy poco comenzaron a sonar en las radios con temas como "yo quisiera" o "Noviembre sin ti", y el éxito les llego de repente, pero los muchachos están convencidos de que llegaron para quedarse, y lo que sigue es una secuencia de lo que fue.
Cargados de de baladas pop y suaves melodías, el nuevo material suena mas maduro desde las notas hasta las letras, y ellos mismos sienten ese cambio al escucharse.
11 canciones nuevas, entre ellas el primer corte "invierno". LetrasLetras de las canciones incluidas en el cd Secuencia de Reik.



Letras de las Canciones Album "Secuencia"♫♪♫♪



(Un día mas) El trío mexicano nos trae su tercer trabajo de estudio. "Un día mas" se titula la placa y supone un cambio en su estilo. Si bien el disco capta la esencia de la agrupación, mezcla nuevos sonidos y juega con las letras para entregar un nuevo producto a su público.
En "Un día mas", el trío se involucra mucho mas en la composición de las letras, 9 de las 11 canciones son de su autoría, y esta vez están acompañados por el afamado productor Gerardo “Cachorro” López junto a Coco Stambuk.
El disco se grabo en Buenos Aires, Argentina, "Inolvidable" es el primer sencillo e incluye la canción "Momentos", que sirve como tema principal de una serie de televisión con el mismo nombre que se transmitirá en México.

Letras de las canciones Album "Un dia mas"♫♪♫♪







Java

El lenguaje Java

Java es un lenguaje orientado a objetos. Esto significa que posee ciertas características que hoy día se consideran estándares en los lenguajes OO:
  • Objetos
  • Clases
  • Métodos
  • Subclases
  • Herencia simple
  • Enlace dinámico
  • Encapsulamiento

Estos conceptos no son simples, por lo que explicaremos su significado más adelante en el curso.

Por el momento, comenzaremos por mostrar el lado oscuro de los objetos:

Para programar orientado a objetos es necesario primero diseñar un conjunto de clases. La claridad, eficiencia y mantenibilidad del programa resultante dependerá principalmente de la calidad del diseño de clases. Un buen diseño de clases significará una gran economía en tiempo de desarrollo y mantención.

Lamentablemente se necesita mucha habilidad y experiencia para lograr diseños de clases de calidad. Un mal diseño de clases puede llevar a programas OO de peor calidad y de más alto costo que el programa equivalente no OO.

Es válido entonces preguntarse: ¿Por qué programar en un lenguaje OO, si se requiere una experiencia que probablemente uno nunca tendrá el tiempo de práctica para llegar a dominarla?

La respuesta a esta pregunta es la siguiente: Java es un lenguaje multiparadigma (como muchos otros lenguajes de programación). Uno no necesita hacer un diseño de clases para programar una aplicación de mil líneas.

¿Entonces por qué no usar otro lenguaje más simple como Visual Basic, si no necesito orientación a objetos?

Porque la ventaja potencial más importante de un lenguaje OO está en las bibliotecas de clases que se pueden construir para él. Una biblioteca de clases cumple el mismo objetivo de una biblioteca de procedimientos en una lenguaje como C. Sin embargo:

Una biblioteca de clases es mucho más fácil de usar que una biblioteca de procedimientos, incluso para programadores sin experiencia en orientación a objetos. Esto se debe a que las clases ofrecen mecanismos de abstracción más eficaces que los procedimientos.

Más adelante quedará clara esta afirmación cuando examinemos ejemplos de bibliotecas de clases.

Por lo tanto podemos distinguir entre varios tipos de programadores en Java:

  • El diseñador de clases: Es el encargado de definir qué clases ofrece una biblioteca y cuál es la funcionalidad que se espera de estas clases. Esta persona tiene que ser muy hábil y de mucha experiencia. Un diseño equivocado puede conducir a clases que son incomprensibles para los clientes de la biblioteca.
  • El programador de clases de biblioteca: Sólo programa la clases especificadas por el diseñador de clases. Esta persona debe entender orientación a objetos, pero no requiere mayor experiencia en diseño de clases.
  • El cliente de bibliotecas: Es el programador de aplicaciones. Él sólo usa las clases que otros han diseñado y programado. Como en el caso anterior necesita entender orientación a objetos y conocer la biblioteca que va usar, pero no necesita mayor experiencia.

Tanto programadores de clases como clientes de bibliotecas pueden llegar a convertirse en buenos diseñadores de clases en la medida que adquieran experiencia, comparando los diseños de las bibliotecas que utilicen.

Por lo tanto es importante destacar que no se necesita gran experiencia en diseño orientado a objetos para poder aprovechar las ventajas de la orientación a objetos.


La Simplicidad de Java

Java ha sido diseñado de modo de eliminar las complejidades de otros lenguajes como C y C++.

Si bien Java posee una sintaxis similar a C, con el objeto de facilitar la migración de C hacia a Java, Java es semánticamente muy distinto a C:

  • Java no posee aritmética de punteros: La aritmética de punteros es el origen de muchos errores de programación que no se manifiestan durante la depuración y que una vez que el usuario los detecta son difíciles de resolver.

  • No se necesita hacer delete: Determinar el momento en que se debe liberar el espacio ocupado por un objeto es un problema difícil de resolver correctamente. Esto también es el origen a errores difíciles de detectar y solucionar.

  • No hay herencia múltiple: En C++ esta característica da origen a muchas situaciones de borde en donde es difícil predecir cuál será el resultado. Por esta razón en Java se opta por herencia simple que es mucho más simple de aprender y dominar.


Java posee bibliotecas de clases estándares

Toda implementación de Java debe tener las siguientes bibliotecas de clases:

  • Manejo de archivos

  • Comunicación de datos

  • Acceso a la red internet

  • Acceso a bases de datos

  • Interfaces gráficas
La interfaz de programación de estas clases es estándar, es decir en todas ellas las operaciones se invocan con el mismo nombre y los mismos argumentos.


Java es multiplataforma

Los programas en Java pueden ejecutarse en cualquiera de las siguientes plataformas, sin necesidad de hacer cambios:

  • Windows/95 y /NT
  • Power/Mac
  • Unix (Solaris, Silicon Graphics, ...)

La compatibilidad es total:

  • A nivel de fuentes: El lenguaje es exactamente el mismo en todas las plataformas.
  • A nivel de bibliotecas: En todas las plataformas están presentes las mismas bibliotecas estándares.
  • A nivel del código compilado: el código intermedio que genera el compilador es el mismo para todas las plataformas. Lo que cambia es el intérprete del código intermedio.


El Look-and-Feel

Lo único que varia de acuerdo a la plataforma es el look-and-feel. Un programa en Windows/95 tendrá el aspecto característico de esta plataforma (en cuanto a la forma de los botones, barras de deslizamiento, menúes, etc.). El mismo programa en Unix tendrá el aspecto característico de Motif. Y en Power/Mac se verá como un programa para Macintosh.

Sin embargo el código que escriben los programadores no tiene que tener presente las características de ninguna de estas plataformas. Es la implementación de la interfaz gráfica estándar de Java la que se encarga de desplegar las ventanas con el look-and-feel de la plataforma local.


C es poco robusto

Se dice que el lenguaje C es un lenguaje poco robusto porque a menudo un error de programación se traduce en un mensaje críptico del estilo segmentation fault. Este tipo de mensajes se origina en 4 errores clásicos:

  • Se accesa un elemento de un arreglo con un índice fuera de rango.
    Ejemplo: a[-3]=5;
  • Se usa un puntero como si referenciara a una estructura de tipo A, cuando en realidad en esa área de memoria hay una estructura de tipo B, incompatible con A. En C esto ocurre debido al uso de casts.
    Ejemplo: *(int*)pdistance
  • Se usa un puntero a una estructura cuyo espacio ya se liberó. Luego volveremos a hablar de este punto.
    Ejemplo: free(p); *p= 1;
  • Al usar aritmética de punteros se comete un error.
    Ejemplo: *(p+i*sizeof(*p))

Todos estos errores conducen a que tarde o temprano se use un puntero que direcciona un área de memoria no asignada por el sistema operativo. Esto es lo que detiene la ejecución con el mensaje segmentation fault.

Lo más desagradable de este tipo de errores es que es muy difícil determinar en qué línea del código está la verdadera fuente del error. Podría ser en cualquier parte del programa. Encontrar la línea puede llevar varios días y hasta semanas, incluso en el caso de programadores expertos.

Java sí es robusto

En Java no se pueden cometer los 4 errores mencionados:

  • Java siempre chequea los índices al accesar un arreglo.
  • Java realiza chequeo de tipos durante la compilación (al igual que C). En una asignación entre punteros el compilador verifica que los tipos sean compatibles.

    Además, Java realiza chequeo de tipos durante la ejecución (cosa que C y C++ no hacen). Cuando un programa usa un cast para accesar un objeto como si fuese de un tipo específico, se verifica durante la ejecución que el objeto en cuestión sea compatible con el cast que se le aplica. Si el objeto no es compatible, entonces se levanta una excepción que informa al programador la línea exacta en donde está la fuente del error.

  • Java posee un recolector de basuras que administra automáticamente la memoria. Es el recolector el que determina cuando se puede liberar el espacio ocupado por un objeto. El programador no puede liberar explícitamente el espacio ocupado por un objeto.
  • Java no posee aritmética de punteros, porque es una propiedad que no se necesita para programar aplicaciones. En C sólo se necesita la aritmética de punteros para programa malloc/free o para programar el núcleo del sistema operativo.

Por lo tanto Java no es un lenguaje para hacer sistemas operativos o administradores de memoria, pero sí es un excelente lenguaje para programar aplicaciones.


Java es flexible

Pascal también es un lenguaje robusto, pero logra su robustez prohibiendo tener punteros a objetos de tipo desconocido. Lamentablemente esta prohibición es demasiado rígida. Aunque son pocos los casos en que se necesita tener punteros a objetos de tipo desconocido, las contorsiones que están obligados a realizar los programadores cuando necesitan estos punteros dan origen a programas ilegibles.

Lisp por su parte es un lenguaje flexible y robusto. Todas las variables son punteros a objetos de cualquier tipo (un arreglo, un elemento de lista, etc.). El tipo del objeto se encuentra almacenado en el mismo objeto. Durante la ejecución, en cada operación se chequea que el tipo del objeto manipulado sea del tipo apropiado. Esto da flexibilidad a los programadores sin sacrificar la robustez. Lamentablemente, esto hace que los programas en Lisp sean poco legibles debido a que al estudiar su código es difícil determinar cuál es el tipo del objeto que referencia una variable.

Java combina flexibilidad, robustez y legibilidad gracias a una mezcla de chequeo de tipos durante la compilación y durante la ejecución. En Java se pueden tener punteros a objetos de un tipo específico y también se pueden tener punteros a objetos de cualquier tipo. Estos punteros se pueden convertir a punteros de un tipo específico aplicando un cast, en cuyo caso se chequea en tiempo de ejecución de que el objeto sea de un tipo compatible.

El programador usa entonces punteros de tipo específico en la mayoría de los casos con el fin de ganar legibilidad y en unos pocos casos usa punteros a tipos desconocidos cuando necesita tener flexibilidad. Por lo tanto Java combina la robustez de Pascal con la flexibilidad de Lisp, sin que lo programas pierdan legibilidad en ningún caso.


Java administra automáticamente la memoria

En Java los programadores no necesitan preocuparse de liberar un trozo de memoria cuando ya no lo necesitan. Es el recolector de basuras el que determina cuando se puede liberar la memoria ocupada por un objeto.

Un recolector de basuras es un gran aporte a la productividad. Se ha estudiado en casos concretos que los programadores han dedicado un 40% del tiempo de desarrollo a determinar en qué momento se puede liberar un trozo de memoria.

Además este porcentaje de tiempo aumenta a medida que aumenta la complejidad del software en desarrollo. Es relativamente sencillo liberar correctamente la memoria en un programa de 1000 líneas. Sin embargo, es difícil hacerlo en un programa de 10000 líneas. Y se puede postular que es imposible liberar correctamente la memoria en un programa de 100000 líneas.

Para entender mejor esta afirmación, supongamos que hicimos un programa de 1000 líneas hace un par de meses y ahora necesitamos hacer algunas modificaciones. Ahora hemos olvidado gran parte de los detalles de la lógica de este programa y ya no es sencillo determinar si un puntero referencia un objeto que todavía existe, o si ya fue liberado. Peor aún, suponga que el programa fue hecho por otra persona y evalúe cuan probable es cometer errores de memoria al tratar de modificar ese programa.

Ahora volvamos al caso de un programa de 100000 líneas. Este tipo de programas los desarrolla un grupo de programadores que pueden tomar años en terminarlo. Cada programador desarrolla un módulo que eventualmente utiliza objetos de otros módulos desarrollados por otros programadores. ¿Quién libera la memoria de estos objetos? ¿Cómo se ponen de acuerdo los programadores sobre cuándo y quién libera un objeto compartido? ¿Como probar el programa completo ante las infinitas condiciones de borde que pueden existir en un programa de 100000 líneas?

Es inevitable que la fase de prueba dejará pasar errores en el manejo de memoria que sólo serán detectados más tarde por el usuario final. Probablemente se incorporán otros errores en la fase de mantención.

Se puede concluir:

  • Todo programa de 100000 líneas que libera explícitamente la memoria tiene errores latentes.
  • Sin un recolector de basuras no hay verdadera modularidad.
  • Un recolector de basuras resuelve todos los problemas de manejo de memoria en forma trivial.

La pregunta es: ¿Cuál es el impacto de un recolector de basura en el desempeño de un programa?

El sobrecosto de la recolección de basuras no es superior al 100%. Es decir si se tiene un programa que libera explícitamente la memoria y que toma tiempo X, el mismo programa modificado de modo que utilice un recolector de basuras para liberar la memoria tomará un tiempo no superior a 2X.

Este sobrecosto no es importante si se considera el periódico incremento en la velocidad de los procesadores. El impacto que un recolector de basura en el tiempo de desarrollo y en la confiabilidad del software resultante es muchos más importante que la pérdida en eficiencia.


Resumen

Java es un lenguaje que ha sido diseñado para producir software:

  • Confiable: Minimiza los errores que se escapan a la fase de prueba.
  • Multiplataforma: Los mismos binarios funcionan correctamente en Windows/95 y /NT, Unix/Motif y Power/Mac.
  • Seguro: Applets recuperados por medio de la red no pueden causar daño a los usuarios.
  • Orientado a objetos: Beneficioso tanto para el proveedor de bibliotecas de clases como para el programador de aplicaciones.
  • Robusto: Los errores se detectan en el momento de producirse, lo que facilita la depuración.

Vector (Java)

La clase Vector

La clase Vector es parte del paquete java.util de la librería estándar de clases de Java. Ofrece un servicio similar a un arreglo, ya que se pueden almacenar y accesar valores y referencias a través de un índice. Pero mientras un arreglo es de cierto tamaño dado, un objeto de tipo Vector puede dinámicamente crecer y decrecer conforme se vaya necesitando. Un elemento puede insertarse y eliminarse de una posición específica a través de la invocación de un sólo método.

A diferencia de un arreglo, un Vector no está declarado para ser de un tipo particular. Un objeto de tipo Vector maneja una lista de referencias a la clase Object, así no pueden almacenarse tipos de datos primitivos.


Algunos de los métodos de la clase Vector se muestran a continuación:


Vector ( )
Constructor: crea un vector inicialmente vacío
void addElement (Objet obj)
Inserta el objeto especificado al final del vector
void setElementAt (Object obj, int indíce)
Inserta el objeto específicado en el vector en la posición específicada
Object remove (int indíce)
Elimina el objeto que se encuentra en la posición específicada y lo regresa
boolean removeElement (Object obj)
Elimina la primera occurencia del objeto específicado en el vector
void removeElementAt (int indíce)
Elimina el objeto específicado en el índice del vector
void clear ( )
Elimina todos los objetos del vector
boolean contains (Object obj)
Regresa verdadero si el objeto dado pertenece al vector
int indexOf (Object obj)
Regresa el índice del objeto específicado. Regresa -1 si no fue encontrado el objeto
Object elementAt (int indíce)
Regresa el componente en el índice específicado
boolean isEmpty ( )
Regresa verdadero si el vector no contiene elementos
int size ( )
Regresa el número de elementos en el vector


Ejemplo:


import java.util.Vector;
/**
* Demuestra el uso de un objeto de la clase Vector
*/

public class Beatles
{
public static void main ()
{
Vector band = new Vector ();
band.addElement ("Paul");
band.addElement ("Pete");
band.addElement ("John");
band.addElement ("George");

System.out.println (band);

band.removeElement ("Pete");

System.out.println (band);
System.out.println ("En la posición 1 está: " + band.elementAt (1));

band.insertElementAt ("Ringo", 2);

System.out.println ("Tamaño de la banda: " + band.size ());
for (int i = 0; i <>
Si se necesitan añadir valores de datos primitivos a un Vector se pueden utilizar las clases conocidas como envoltorios que son: Integer, Long, Double y Float. Sus métodos de conversión respectivos son: intValue ( ), longValue ( ), doubleValue ( ) y floatValue ( ).

viernes, 3 de abril de 2009

Threads (Java)

Introducción

En este artículo voy a explicar cómo se usan los threads en Java (también traducidos como "hilos de ejecución"). La intención no es solamente explicar cuáles son las funciones que hay que llamar si no, también, dar un pantallazo de con qué problemas uno se puede encontrar al crear programas multithread, de qué herramientas se dispone para evitar esos problemas y de cómo utilizarlas.

Cómo se usan

Java tiene un buen soporte de threads. Para usarlo solamente hay que crear un objeto de la clase Thread y pasarle un Runnable. Un Runnable es un objeto que, "implementando" esa interfaz, promete al mundo contar con un método run(). El nuevo thread iniciado comenzará su ejecución saltando a este método y cuando éste termine el thread terminará.

Ejemplo:

Thread t = new Thread("Thread para contar", new Runnable() {
void run()
{
for(int i = 1 ; i <= 10 ; i++)
System.out.println(i);
}
});
t.start();

/* Acá, para ejemplificar, llamamos a un método que tarda,
* por ejemplo porque espera que se tipee enter. Mientras
* tanto, en la pantalla va apareciendo la cuenta hasta diez
* que sucede en el thread.
*/

in.readLine();

¡No confundir el método start con el método run! El método run contiene el código a ser ejecutado “asíncronamente” en otro thread, mientras que el método start es el que crea el thread y en algún punto hace que ese thread ejecute lo que está en run. Este método devuelve el control inmediatamente. Pero si mezclamos todo y ejecutamos directamente run(), el código se ejecutará en el thread actual!

El método start() devuelve el control inmediatamente... mientras tanto, el nuevo thread inicia su recorrido por el método run(). ¿Hasta cuándo? Hasta que termina ese método, cuando sale, termina el thread. Si un thread necesita esperar a que otro termina (por ejemplo el thread padre esperar a que termine el hijo) puede usar el método join(). ¿Por qué se llama así? Bueno, crear un proceso es como una bifuración, se abren dos caminos... que uno espere a otro es lo contrario, una unificación.

Sincronización

La necesidad

La cuestión cuando se trabaja con threads es que la ejecución avanza en varias partes del programa a la vez. Cada una de esas ejecuciones simultáneas pueden tocar los mismos objetos. Eso a veces es un problema. Un ejemplo: Suponga que un thread encola pedidos e incrementa un contador. Existen además 50 threads que se fijan si el contador es mayor que cero y si lo es retiran un pedido, decrementan el contador, y procesan la tarea. Supongamos que hay un pedido en la cola, el contador vale 1, y que sucede lo siguiente:

  1. El thread A comprueba que el contador vale más que cero.
  2. El thread B comprueba que el contador vale más que cero.
  3. Basado en su comprobación el thread B decrementa el contador y toma el pedido.
  4. Basado en su comprobación el thread A decrementa el contador y toma el ped... auch, ya no hay pedido!

¿Qué pasó acá? El thread A miró, vio algo y se dispuso a actuar, pero cuando actuó alguien se le había metido en el medio. El mundo ya no era el que era cuando él tomó la decisión de actuar. El problema, generalizado, es el espacio de tiempo que hay entre mirar y actuar, cuando el mundo en el que se mira es compartido por más de un actor. A este tipo de problemas se les llama condición de carrera (en inglés “race condition”), porque son como una competencia.

La solución del problema

Para evitar el caso que expuse lo que se hace es establecer un “lock”, un bloqueo. En Java cada objeto tiene asignado algo que se le llama “monitor”. Mediante la palabra clave synchronized un thread puede “tomar” el monitor. Si otro thread intenta tomar el monitor del mismo objeto, el segundo thread se bloquea hasta que el primero suelte ese monitor.

Entonces, el ejemplo anterior modificado por la sabiduría de los bloqueos quedaría así:

  1. El thread A intenta tomar el monitor del objeto de la cola. Lo consigue: es suyo.
  2. El thread A comprueba que el contador vale más que cero.
  3. El thread B intenta tomar el monitor del objeto de la cola. No lo consigue: lo tiene A, inicia espera.
  4. Basado en su comprobación el thread A decrementa el contador y toma el pedido.
  5. El thread A libera el monitor.
  6. Al quedar el monitor liberado el thread B continúa. Ahora tiene el monitor.
  7. El thread B comprueba que el contador vale cero.
  8. Basado en su comprobación el thread B ve que no tiene nada que hacer.
  9. El thread B libera el monitor.

Sintaxis para establecer locks

Como se dijo, la toma de un monitor se debe hacer con la palabra clave synchronized. Esta palabra tiene dos formas de ser usada. La primera y más básica forma de usar synchronized es la siguiente:

synchronized(objeto)
{
// instrucciones
}

El intérprete Java toma el monitor del objeto y ejecuta las instrucciones del bloque. En cualquier caso en el que se salga del bloque el monitor es liberado. Esto incluye la salida por causa de alguna excepción que se produzca. El mecanismo asegura que no exista el riesgo de que se salga de ese bloque sin liberar el monitor, lo que probablemente tarde o temprano haría que se congele todo el programa.

Es de notar que el uso del monitor de un objeto no tiene nada que ver con el uso del objeto mismo. Mientras el código que deba ser “mutuamente excluido” se sincronice sobre el mismo objeto... ¡no importa qué papel tome ese objeto en el gran esquema de las cosas!

Retomando el ejemplo anterior, una implementación del método que toma un pedido podría ser (antes de aplicar la sabiduría de threads aquí explicada):

class MesaDeEntradas
{
int n = 0;
Queue cola;

Pedido retirarPedido()
{
if(n > 0)
{
n--;
return cola.remove(0);
}
return null;
}
}

Los dos momentos críticos en los que dos threads separados pueden molestarse entre sí son claramente el momento del if y el momento en el que se invoca al método remove(0). Cuando un thread está preguntando el valor de n no debe haber ninguno otro ni preguntando el valor de n, ni removiendo el valor de la pila. Esto podemos lograrlo tomando el monitor antes de todo este código y liberándolo después:

class MesaDeEntradas
{
int n = 0;
Queue cola;

Pedido retirarPedido()
{
synchronized(this)
{
if(n > 0)
{
n--;
return cola.remove(0);
}
return null;
}
}
}

Hay dos salidas posibles del bloque sincronizado, ya que hay dos instrucciones return. Pero no hay problema con eso, ya que como se dijo cualquiera sea la forma en que se abandone el bloque, el monitor tomado será liberado correctamente.

Es interesante ver que el hecho de sincronizar sobre this no obedece sino a una convención completamente arbitraria decidida por el programador. Ya que el objeto this es el que está de alguna manera englobando a toda la funcionalidad provista, es lógico usarlo para sincronizar. Pero hay que notar que cualquier otro método de esta clase que use this en un bloque synchronized se sincronizará también con este bloque! Si esto no es lo que se quiere se deberá entonces elegir cualquier otro objeto. A veces tiene sentido crear un objeto solamente para usar su monitor.

Como es muy común querer usar this para sincronizarse, se introdujo una ayudita sintáctica que consiste en una forma abreviada de tomar el monitor de la instancia en la que ese está ejecutando un método. De esta manera...

synchronized void m()
{
// ...
}

... equivale a ...

void m()
{
synchronized(this)
{
// ...
}
}

Por lo tanto podemos reescribir el código anterior como...

class MesaDeEntradas
{
int n = 0;
Queue cola;

synchronized Pedido retirarPedido()
{
if(n > 0)
{
n--;
return cola.remove(0);
}
return null;
}
}

Esperando locks

En el ejemplo que di más arriba teníamos threads que periódicamente se fijaban si un contador era mayor a 0 para saber si había pedidos a procesar. ¿Cada cuánto debería uno fijarse si hay pedidos? ¿A cada minuto? ¿Cada tres? ¿segundos?

En el caso de los ejemplos anteriores, el método retirarPedido devuelve null si no hay todavía ningún pedido esperando (es decir, n == 0). Ahora, supongamos que tenemos 10 threads creados al solo efecto de procesar pedidos. Cada uno de esos threads querrá quedarse esperando si no hay un pedido.

La manera torpe de hacerlo:

 Pedido p = null;
while(true)
{
p = m.retirarPedido();
if(p != null)
break;
else
Thread.sleep(1000); // esperamos un segundo
}

Pero creo que es claro que no es eficiente hacerlo así. Además en muchos casos no conviene esperar todo un segundo para procesar lo que hay que procesar.

Las APIs de sincronización en casi todos los lenguajes y plataformas siempre proveen mecanismos de espera de condiciones. En el caso de Java la cosa se hace como explico a continuación. Y acá es donde aparecen wait y notify.

notify:
Como disparar el tiro que inicia una carrera.
wait:
El corredor que espera ese tiro para correr.

El thread se “duerme” sobre un monitor. Cuando otro thread “sacude” el monitor, el bello durmiente se despierta y continúa. Esto se hace con las operaciones wait, notify y notifyAll.

Entonces, podría parecer que la cosa se resuelve así: En la "MesaDeEntradas", cuando llega un pedido, se deberá hacer:

class MesaDeEntradas
{
synchronized void métodoQueRecibeUnPedido(Pedido p)
{
cola.add(p);
notifyAll();
}
}

Y si no somos muy conocedores del tema nos podría parecer que el otro lado del código (el consumidor de la información) se vería algo como esto:

 m.wait();
Pedido p = m.retirarPedido();

... pero ¿cómo vamos a esperar sin preguntar antes? ¿Qué pasa si ya había un pedido? Nos quedaríamos esperando en vano, entonces mejor preguntamos:

 // si no hay un pedido, esperamos.
if(!m.hayPedido())
m.wait();
Pedido p = retirarPedido();

Si algo sacó usted de la lectura de la sección anterior, debería haber descubierto el problema que aparece. Examinamos el mundo (preguntando si hay pedido)... y después actuamos (iniciar la espera). ¿Qué pasa si el pedido llega en el medio y el notify (en otro thread) sucede antes de que nos pongamos a esperar? Nos quedaríamos esperando un notify que nunca llegará.

Por lo dicho es importante tomar "el monitor" del objeto a esperar antes de decidir hacerlo. Pero en el momento inmediatamente después de iniciada la espera el monitor debe ser soltado para que el notify (que intentará tomar ese mismo monitor) pueda suceder. Sería imposible sin ayuda del lenguaje soltar algo mientras estamos en un wait(), ya que la ejecución está suspendida. Por eso, al entrar en el wait se libera atómicamente el monitor. La entrada en el wait, y soltar el "lock" sobre el objeto suceden como una operación indivisible. Repito: Al llamar a wait() sobre un objeto... automáticamente se libera el monitor (¡sin haber salido del synchronized!). Cuando wait termina, automáticamente se recupera el monitor.

Entoces aplicando todo esto correctamente la espera se escribe así:

 Pedido p = null;

synchronized(m)
{
m.wait();
p = retirarPedido();
}

Sockets (Java)

A. Fundamentos

Los sockets son un sistema de comunicación entre procesos de diferentes máquinas de una red. Más exactamente, un socket es un punto de comunicación por el cual un proceso puede emitir o recibir información.

Fueron popularizados por Berckley Software Distribution, de la universidad norteamericana de Berkley. Los sockets han de ser capaces de utilizar el protocolo de streams TCP (Transfer Contro Protocol) y el de datagramas UDP (User Datagram Protocol).

Utilizan una serie de primitivas para establecer el punto de comunicación, para conectarse a una máquina remota en un determinado puerto que esté disponible, para escuchar en él, para leer o escribir y publicar información en él, y finalmente para desconectarse.

B. Ejemplo de uso

Para comprender el funcionamiento de los sockets no hay nada mejor que estudiar un ejemplo. El que a continuación se presenta establece un pequeño diálogo entre un programa servidor y sus clientes, que intercambiarán cadenas de información.

a.) Programa Cliente

El programa cliente se conecta a un servidor indicando el nombre de la máquina y el número puerto (tipo de servicio que solicita) en el que el servidor está instalado.

Una vez conectado, lee una cadena del servidor y la escribe en la pantalla:

import java.io.*;


import java.net.*;


class Cliente {


static final String HOST = "localhost";


static final int PUERTO=5000;


public Cliente( ) {


try{


Socket skCliente = new Socket( HOST , Puerto );


InputStream aux = skCliente.getInputStream();


DataInputStream flujo = new DataInputStream( aux );


System.out.println( flujo.readUTF() );


skCliente.close();


} catch( Exception e ) {


System.out.println( e.getMessage() );


}


}


public static void main( String[] arg ) {


new Cliente();


}


}


En primer lugar se crea el socket denominado skCliente, al que se le especifican el nombre de host (HOST) y el número de puerto (PORT) en este ejemplo constantes.

Luego se asocia el flujo de datos de dicho socket (obtenido mediante getInputStream)), que es asociado a un flujo (flujo) DataInputStream de lectura secuencial. De dicho flujo capturamos una cadena ( readUTF() ), y la imprimimos por pantalla (System.out).

El socket se cierra, una vez finalizadas las operaciones, mediante el método close().

Debe observarse que se realiza una gestión de excepción para capturar los posibles fallos tanto de los flujos de datos como del socket.

b.) Programa Servidor

El programa servidor se instala en un puerto determinado, a la espera de conexiones, a las que tratará mediante un segundo socket.

Cada vez que se presenta un cliente, le saluda con una frase "Hola cliente N".

Este servidor sólo atenderá hasta tres clientes, y después finalizará su ejecución, pero es habitual utilizar bucles infinitos ( while(true) ) en los servidores, para que atiendan llamadas continuamente.

Tras atender cuatro clientes, el servidor deja de ofrecer su servicio:

import java.io.* ;


import java.net.* ;


class Servidor {


static final int PUERTO=5000;


public Servidor( ) {


try {


ServerSocket skServidor = new ServerSocket( PUERTO );


System.out.println("Escucho el puerto " + PUERTO );


for ( int numCli = 0; numCli <>


Socket skCliente = skServidor.accept(); // Crea objeto


System.out.println("Sirvo al cliente " + numCli);


OutputStream aux = skCliente.getOutputStream();


DataOutputStream flujo= new DataOutputStream( aux );


flujo.writeUTF( "Hola cliente " + numCli );


skCliente.close();


}


System.out.println("Demasiados clientes por hoy");


} catch( Exception e ) {


System.out.println( e.getMessage() );


}


}


public static void main( String[] arg ) {


new Servidor();


}


}



Protocolo TCP/IP

Introducción:

En esta sección vamos a explorar una de las características más interesantes de Java: su capacidad para integrarse en redes TCP/IP. Esto permite usar este lenguaje para construir aplicaciones distribuidas en muy poco tiempo.

A lo largo de esta sección asumiremos que el lector ya conoce suficientemente los aspectos de Java relacionados con Entrada/Salida, trabajo con flujos de datos, manejo de excepciones, etc.

Los pasos que seguiremos serán los siguientes: primero presentaremos los conceptos básicos de los protocolos de Internet, necesarios para la discusión posterior; a continuación veremos la forma que tiene Java de implementar dichos conceptos; finalmente analizaremos pequeños programas que nos muestren la mejor forma de utilizar las herramientas que Java pone a nuestra disposición.

Nota:Este capítulo no pretende ser una introducción genérica al funcionamiento de la pila de protocolos TCP/IP. Por ello recomendamos la lectura de obras de referencia que cumplen este objetivo. Para el lector interesado exclusivamente en el funcionamiento interno de TCP/IP, sugerimos "Internetworking with TCP/IP", de Douglas E. Comer. Para el que busque un texto más didáctico y que cubra el tema desde un punto de vista más generalista, es interesante "Computer Networks", de Andrew S. Tanenbaum. Ambos editados en inglés por Prentice Hall.

Toda la documentación original sobre los protocolos utilizados se encuentran disponibles como documentos RFC y STD en Internet. Uno de los "mirrors" españoles se encuentra en RedIris.

Conceptos básicos

Direcciones IP

Como el lector sabrá, todas las máquinas conectadas a una red IP (Internet Protocol), bien sea la red pública Internet o una red privada, se distinguen por su dirección IP. Esta dirección IP es un número de 32 bits, que por comodidad suele expresarse en forma de 4 números decimales separados por puntos. Cada uno de estos números se corresponde con 8 bits de la dirección IP. Por ejemplo: 209.41.57.70 es una dirección IP.

Esta dirección IP puede ser fija o puede ser distinta cada vez que la máquina se conecta a la red. Esto es lo que ocurre a casi todos los usuarios que se conectan a Internet a través de la línea telefónica.

Nombres de dominio

Para facilitar todavía más el trabajo con direcciones IP, existen los nombres de dominio. Estos nombres de dominio son cadenas alfanuméricas, más fáciles de recordar, y que suelen tener una única dirección IP asociada. Siguiendo con el ejemplo anterior, akal.com es el nombre de dominio asociado con la dirección IP 209.41.57.70.

Los encargados de traducir los nombres de dominios en dirección IP son los servidores DNS (Domain Name Server). Estos servidores DNS mantienen unas tablas de correspondencias entre direcciones y dominios. Estas tablas se actualizan periódicamente a medida que los distintos servidores DNS intercambian sus datos entre sí.

Puertos

La forma general de establecer una comunicación a través de Internet es: 1) Indicar la dirección IP de la máquina con la que queremos conectar y 2) especificar el número de puerto dentro de esa máquina a través del cual queremos establecer la comunicación.

Para que la comunicación se pueda establecer debe haber un proceso en esa máquina "escuchando" en el puerto especificado. Generalmente, cada máquina tiene una serie de servicios escuchando en ciertos puertos standard: el servidor HTTP (servidor Web) en el puerto 80, el servidor FTP en el puerto 21, el puerto 25 para SMTP (correo electrónico), etc. Estos puertos están asignados en el standard RFC 1700.

Circuitos y Paquetes

La forma más común de transmitir información a través de Internet es mediante los protocolos de transporte TCP (Transport Control Protocol) y UDP (User Datagram Protocol). Estos protocolos se diferencian principalmente en que TCP (definido en RFC 793) está orientado a la conexión (es decir, necesita el establecimiento de una conexión entre ambos extremos y realiza un complejo control de errores), mientras que UDP (definido en RFC 768) se basa en el envío de paquetes individuales, sin establecimiento previo de una conexión y sin control de errores.

Obviamente, cada tipo de comunicación tiene sus ventajas e inconvenientes, y será necesario decidirse por un protocolo u otro en función de las necesidades de cada aplicación.

****************************************************************************************

Mínimo Servidor TCP/IP

Veamos el código que presentamos en el siguiente ejemplo, donde desarrollamos un mínimo servidor TCP/IP, para el cual desarrollaremos después su contrapartida cliente TCP/IP. La aplicación servidor TCP/IP depende de una clase de comunicaciones proporcionada por Java: ServerSocket. Esta clase realiza la mayor parte del trabajo de crear un servidor.

import java.awt.*;
import java.net.*;
import java.io.*;

class minimoServidor {
public static void main( String args[] ) {
ServerSocket s = (ServerSocket)null;
Socket s1;
String cadena = "Tutorial de Java!";
int longCad;
OutputStream s1out;

// Establece el servidor en el socket 4321 (espera 300 segundos)
try {
s = new ServerSocket( 4321,300 );
} catch( IOException e ) {
System.out.println( e );
}

// Ejecuta un bucle infinito de listen/accept
while( true ) {
try {
// Espera para aceptar una conexión
s1 = s.accept();
// Obtiene un controlador de fichero de salida asociado
// con el socket
s1out = s1.getOutputStream();

// Enviamos nuestro texto
longCad = sendString.length();
for( int i=0; i < longCad; i++ )
s1out.write( (int)sendString.charAt( i ) );

// Cierra la conexión, pero no el socket del servidor
s1.close();
} catch( IOException e ) {
System.out.println( e );
}
}
}
}

Mínimo Cliente TCP/IP

El lado cliente de una aplicación TCP/IP descansa en la clase Socket. De nuevo, mucho del trabajo necesario para establecer la conexión lo ha realizado la clase Socket. Vamos a presentar ahora el código de nuestro cliente más simple, que encaja con el servidor presentado antes. El trabajo que realiza este cliente es que todo lo que recibe del servidor lo imprime por la salida estándar del sistema.

import java.awt.*;
import java.net.*;
import java.io.*;

class minimoCliente {
public static void main( String args[] ) throws IOException {
int c;
Socket s;
InputStream sIn;

// Abrimos una conexión con breogan en el puerto 4321
try {
s = new Socket( "breogan",4321 );
} catch( IOException e ) {
System.out.println( e );
}

// Obtenemos un controlador de fichero de entrada del socket y
// leemos esa entrada
sIn = s.getInputStream();
while( ( c = sIn.read() ) != -1 )
System.out.print( (char)c );

// Cuando se alcance el fin de fichero, cerramos la conexión y
// abandonamos
s.close();
}
}

jueves, 2 de abril de 2009

Java Data Mining Framework (JDMF)

JDMF: es un marco que mina de los datos escrito en Java. Las características principales incluyen: simplicidad, flexibilidad, muchos algoritmos de elegir de, muchos formatos de la entrada (e.g. XML, CSV, JDBC, habas de Java) y datos de la salida (e.g. XML, texto llano Info, cartas).。

Estado:

Alfa

Audiencias previstas:

Usuario experimentado Programador

Licencia:

LGPL

OS:

OS Independent

Lenguaje de programación:

JAVA

Asunto:

Almacenamiento de los datos

Programador:

1

Clases (Java)

Las clases son lo más simple de Java. Todo en Java forma parte de una clase, es una clase o describe como funciona una clase. El conocimiento de las clases es fundamental para poder entender los programas Java.

Todas las acciones de los programas Java se colocan dentro del bloque de una clase o un objeto. Todos los métodos se definen dentro del bloque de la clase, Java no soporta funciones o variables globales. Esto puede despistar a los programadores de C++, que pueden definir métodos fuera del bloque de la clase, pero esta posibilidad es más un intento de no separarse mucho y ser compatible con C, que un buen diseño orientado a objetos. Así pues, el esqueleto de cualquier aplicación Java se basa en la definición de una clase.

Todos los datos básicos, como los enteros, se deben declarar en las clases antes de hacer uso de ellos. En C la unidad fundamental son los ficheros con código fuente, en Java son las clases. De hecho son pocas las sentencias que se pueden colocar fuera del bloque de una clase. La palabra clave import (equivalente al #include) puede colocarse al principio de un fichero, fuera del bloque de la clase. Sin embargo, el compilador reemplazará esa sentencia con el contenido del fichero que se indique, que consistirá, como es de suponer, en más clases.

Tipos de Clases

Hasta ahora sólo se ha utilizado la palabra clave public para calificar el nombre de las clases que hemos visto, pero hay tres modificadores más. Los tipos de clases que podemos definir son:

abstract

Una clase abstract tiene al menos un método abstracto. Una clase abstracta no se instancia, sino que se utiliza como clase base para la herencia.

final

Una clase final se declara como la clase que termina una cadena de herencia. No se puede heredar de una clase final. Por ejemplo, la clase Math es una clase final.

public

Las clases public son accesibles desde otras clases, bien sea directamente o por herencia. Son accesibles dentro del mismo paquete en el que se han declarado. Para acceder desde otros paquetes, primero tienen que ser importadas.

synchronizable

Este modificador especifica que todos los métodos definidos en la clase son sincronizados, es decir, que no se puede acceder al mismo tiempo a ellos desde distintos threads; el sistema se encarga de colocar los flags necesarios para evitarlo. Este mecanismo hace que desde threads diferentes se puedan modificar las mismas variables sin que haya problemas de que se sobreescriban.

BitSet

Se llama así lo que en realidad es un Vector de bits. Lo que ocurre es que está optimizado para uso de bits. Bueno, optimizado en cuanto a tamaño, porque en lo que respecta al tiempo de acceso a los elementos, es bastante más lento que el acceso a un array de elementos del mismo tipo básico.

Además, el tamaño mínimo de un BitSet es de 64 bits. Es decir, que si se está almacenando cualquier otra cosa menor, por ejemplo de 8 bits, se estará desperdiciando espacio.

En un Vector normal, la colección se expande cuando se añaden más elementos. En el BitSet ocurre los mismo pero ordenadamente.

Se utiliza el generador de números aleatorios para obtener un byte, un short y un int, que son convertidos a su patrón de bits e incorporados al BitSet.

**************************************************************************************************

Este ejemplo demuestra la utilizacion de BitSets y un poco de su manipulacion:

import java.util.*;

public class java414 {
public static void main(String args[]) {
Random aleat = new Random();

// Coge el bit menos significativo devuelto por nextInt()
byte bt = (byte)aleat.nextInt();
BitSet bbyte = new BitSet();
for( int i=7; i >= 0; i-- ) {
if( ( (1 <<>
bbyte.set( i );
else
bbyte.clear( i );
}
System.out.println( "Valor byte: "+bt );
printBitSet( bbyte );

short st = (short)aleat.nextInt();
BitSet bshort = new BitSet();
for( int i=15; i >= 0; i-- ) {
if( ( (1 <<>
bshort.set( i );
else
bshort.clear( i );
}
System.out.println( "Valor short: "+st );
printBitSet( bshort );

int it = aleat.nextInt();
BitSet bint = new BitSet();
for( int i=31; i >= 0; i-- ) {
if( ( (1 <<>
bint.set( i );
else
bint.clear( i );
}
System.out.println( "Valor int: "+it );
printBitSet(bint);

// Prueba BitSets mayores o iguales a 64 bits
BitSet b1 = new BitSet();
b1.set( 127 );
System.out.println( "Fija el bit 127: "+b1 );

BitSet b2 = new BitSet( 65 );
b2.set(255);
System.out.println( "Fija el bit 255: "+b2 );

BitSet b3 = new BitSet( 512 );
b3.set( 1023 );
System.out.println( "Fija el bit 1023: "+b3 );
}

static void printBitSet( BitSet b ) {
System.out.println( "Bits: "+b );

String bbits = new String();
for( int j=0; j <>
bbits += ( b.get( j ) ? "1" : "0" );
System.out.println( "Patron de bits: "+bbits );
}
}





Free Blogspot Templates by Isnaini Dot Com and Cars Picture. Powered by Blogger