Sincronización Pura en Java

Yo no soy muy de Java, pero estoy haciendo la una práctica sobre concurrencia en Java y bueno he tenido que recordar algunas cosas. Me he hecho una implementación de para la sincronización pura entre 2 hilos en Java.

Existen muchas variantes de la explicación del problema del consumidor / productor. Yo para probar la clase SincronizadorPuro he considerado el problema de la siguiente forma.

  • El consumidor va a consumir N veces
  • El productor va a producir N veces
  • Siempre lo deben hacer alternativamente

El recurso de lo que se consume o produce debe ser protegido, y por tanto realmente estamos hablando de comunicación sincronizada, pero realmente el problema que quiero abordar es el de la sincronización pura. Además que solucionar el problema de comunicación es tan simple como hacer sincronizados los metodos get / set del atributo a proteger.

El diagrama de precedencia sería:

A—-> B —-> A —-> B —- …..—->A ——> B

Este diagrama se resuelve con 2 semaforos iniciados a 0 con máximo 1. Que es lo que abstroigo en la clase SincronizacionPura con los métodos: esperar() / avisar() que corresponden al típico wait / signal de los semáforos.

Por ello el algoritmo para A es simplemente:

  • Esperará a B excepto si es la primera iteración.
  • Se ejecuta el código A, en mi ejemplo pone el contador del for en el recurso compartido.
  • Se avisa a B de que ha terminado.

Y analogamente, el algoritmo de B:

  • Primero esperará a A siempre.
  • Ejecuta el código asociado a B, que en mi caso es imprimir el valor del recurso.
  • Se avisa a A excepto en la última iteración.

Código del ejemplo:

package parquetematico;

import java.util.concurrent.Semaphore;

class Productor implements Runnable
{
    int INICIO;
    int FIN;
    int INC;
    Recurso recurso;
    SincronizadorPuro sync_productor;
    SincronizadorPuro sync_consumidor;

    public Productor(int INICIO, int FIN, int INC, Recurso recurso, SincronizadorPuro sync_productor, SincronizadorPuro sync_consumidor)
    {
        this.INICIO = INICIO;
        this.FIN = FIN;
        this.INC = INC;
        this.recurso = recurso;
        this.sync_productor = sync_productor;
        this.sync_consumidor = sync_consumidor;
    }

    public void run()
    {
        int i = INICIO;

        while (i <= FIN)
        {
            if(INICIO != i)
                sync_consumidor.esperar();

            recurso.setX(i);
            System.out.println("produce " + recurso.getX());

            sync_productor.avisar();

            i+=INC;
        }
    }

}

 class Consumidor implements Runnable
 {

    int INICIO;
    int FIN;
    int INC;
    Recurso recurso;
    SincronizadorPuro sync_productor;
    SincronizadorPuro sync_consumidor;

    public Consumidor(int INICIO, int FIN, int INC, Recurso recurso, SincronizadorPuro sync_productor, SincronizadorPuro sync_consumidor)
    {
        this.INICIO = INICIO;
        this.FIN = FIN;
        this.INC = INC;
        this.recurso = recurso;
        this.sync_productor = sync_productor;
        this.sync_consumidor = sync_consumidor;
    }

    public void run()
    {
        int i = INICIO;

        while (i <= FIN)
        {
            sync_productor.esperar();

            System.out.println("consume " + recurso.getX());

            if(FIN != i)
                sync_consumidor.avisar();

            i+=INC;
        }
    }

}

class SincronizadorPuro
{
    Semaphore sync;

    public SincronizadorPuro(int max) {
        // crea un semaforo en (max, max)
        sync = new Semaphore(max);

        // lo dejamos como (0, max)
        for(int i=0 ; i<max ; i++)
        {
            try {
            sync.acquire();
            } catch (InterruptedException e) {}
        }
    }

    /*
     * Pseudo-implementación conceptual
     *
    WAIT / P / ESPERAR / ACQUIRE
    {
        cont--;
        if (cont < 0)
        {
            while (true)
            {
                try
                {
                    wait();
                    break;
                }
                catch (InterruptedException e)
                {
                    if (cont >= 0)
                        break;
                    else
                        continue;
                }
            }
        }
    }
    */
    public void esperar()
    {
        try {
        sync.acquire();
        } catch (InterruptedException e) {}
    }

    /*
     * Pseudo-implementación conceptual
     *
    SIGNAL/ V / AVISAR / RELEASE
    {
        cont++;
        if (cont <= 0)
            notify();
        if (cont > 1)
            cont = 1;
    }
    */
    public void avisar()
    {
        sync.release();
    }

    public int getContador()
    {
        return sync.availablePermits();
    }
}

class Recurso
{
    private int x;

    public Recurso(int x)
    {
        this.x = x;
    }

    public synchronized void setX(int x)
    {
        this.x = x;
    }

    public synchronized int getX()
    {
        return x;
    }

}

public class principal
{

    public static void main(String[] args)
    {
        Recurso r1 = new Recurso(0);
        SincronizadorPuro sync_productor = new SincronizadorPuro(1);
        SincronizadorPuro sync_consumidor = new SincronizadorPuro(1);

        // produce/consume desde 0 hasta 5000 en pasos de 1
        Productor p1 = new Productor(0,5000, 1, r1, sync_productor, sync_consumidor);
        Consumidor c1 = new Consumidor(0,5000, 1, r1, sync_productor, sync_consumidor);
        Thread h1 = new Thread(p1);
        Thread h2 = new Thread(c1);
        h1.start();
        h2.start();
        try
        {
            h1.join();
            h2.join();
        }
        catch(InterruptedException e) {}
    }

}

Unison: Sincronización de carpetas en local o remoto

Os pondre un ejemplo para sincronizar dos carpetas remotas, Unison en remoto usa ssh. Asi que necesitamos instalar ssh y unison. Asi que lo primero :

sudo apt-get install ssh unison unison-gtk

Con esto nos instala un cliente de SSH, pone un servidor de SSH en el puerto por defecto. Y además porsupuesto el unison y su interfaz gráfico.

Me voy a basar en un ejemplo para sincronizar los mapas del Warcraft III. Para serviriía evidentemente para sincronizar 2 carpetas cualesquiera.

Son 2 portatiles en LAN con IPs 192.168.1.4 y 192.168.1.5. Ambos PCs juegan bastante mapas muy variados y al poco tiempo un ordenador tiene más mapas que otro, o tiene más mapas de tipo td, vampiro y menos de otro tipo.

En definitiva queremos que todos los mapas que tenga el 4 y no el 5 se copien el 5. Y todos los mapas que tenga el 5 y no tengo el 4 se copien al 4. De manera que ambas carpetas queden exactemente iguales.

Por ultimo se quiere comprobar si hay mapas nuevos cada 5 min, no es problema, unison es muy eficiente, y robusto. Tienes modos para trabajar en batch (proceso de fondo, con poca prioridad y no interactivo, el usuario no interviene). Bueno eso de que unison no pregunte puede asustar, eso no es un problema, porque unison solo sincroniza lo que sabe perfectamente que puede modificar sin peligro de que el usuario se emcabrone por perder información. Cada cierto tiempo el usuario puede entrar por el interfaz gráfico del unison y resolver los posibles conflictos de sincronización.

Sigue leyendo

SSH sin contraseña (usando certificados DSA)

ssh tunelPara autentificarnos por ssh hay 2 opciones:

  1. Por la contraseña clásica. Es el método síncrono tiene como inconveniente que hay que introducir la contraseña cada vez que intentamos autentificarlos.
  2. Mediante certificado RSA o DSA. Es el método asíncrono. Resumidamente consiste en generar una clave privada y otra pública, y distribuir la clave pública a los servidores que queremos conectar. Por supuesto la ventaja es que al no pedir contraseña podemos automatizar scripts junto con cron. Por ejemplo realizar una sincronización con unison entre 2 carpetas remotas remotas cada 5 minutos.

Puesto que la primera opción no tiene mucho misterio, voy a explicar como generar la clave y distribuirla, para realizar estos 2 tareas vamos hacer 2 alias, por que es normal que usemos frecuentemente estos comandos algo enrevesados nos ayudamos de los alias:

  • Abrimos el .bashrc : sudo gedit .bashrc y al final del fichero añadimos estos 2 líneas y guarda :
    alias generarClaveSSH='ssh-keygen -t dsa'
    
    alias distribuirClaveSSH='ssh-copy-id -i .ssh/id_dsa.pub'
  • Ahora vamos a generar las claves mediante DSA que corresponde al SSH2, teniendo muchos problemas menos de seguridad que RSA. Para ellos vamos a usar los alias, pero antes abre otra consola para que carge los alias : Escribimos generarClaveSSH . Le damos al enter a todas las preguntas, no escribimos nada, de esta manera no protegemos el certificado SIN un passphrase. Si hubieramos puesto un passphrase cada vez que conectasemos tendríamos que indicar un passphrase nuestro, asi que en principio estaríamos con el mismo problema que antes. Esto se usa para centralizar todas las claves en 1. Si le interesa a alguien lo puedo explicar un poco más, pero veo poco útil especificar un passphrase para el uso que le estoy dando.
  • Ahora simplemente falta distribuir la clave al ordenador remoto para ello necesitamos la IP y el USUARIO, escribimos: distribuirClaveSSH USUARIO@IP. En este momento os preguntara la contraseña de la cuenta USUARIO, la introducimos y a partir de ahora el servidor SSH montado en IP te aceptara directamente por que tiene tu clave pública.
  • Para probarlo como sabreis ssh USUARIO@IP

Nada más, espero que le sirve a alguien, dentro de unos días ampliare este post explicando el unison.

Editado: Aqui esta el post de unison : https://blogricardo.wordpress.com/2008/08/01/unison-sincronizacion-de-carpetas-en-local-o-remoto/