Categorías
Concurrency

Java Thread: ejemplos denotificar () y esperar ()

Este artículo contiene dos ejemplos de código para demostrar la simultaneidad de Java. Representan un uso muy típico. Al comprenderlos, comprenderá mejor las notificaciones () y la espera ().

1. Algunos conocimientos previos

  1. synchronized La palabra clave se utiliza para acceso exclusivo.
  2. Para sincronizar un método, simplemente agregue la palabra clave sincronizada a su declaración. Entonces no se pueden intercalar dos invocaciones de métodos sincronizados en el mismo objeto.
  3. Las declaraciones sincronizadas deben especificar el objeto que proporciona el bloqueo intrínseco. Cuando se utiliza sincronizado (esto), debe evitar sincronizar las invocaciones de los métodos de otros objetos.
  4. wait() le dice al hilo de llamada que abandone el monitor y se vaya a dormir hasta que otro hilo entre en el mismo monitor y llame anotificar ().
  5. notify() despierta el primer hilo que llamó wait() en el mismo objeto.

2. notificar () y esperar () – ejemplo 1

public class ThreadA {
    public static void main(String[] args){
        ThreadB b = new ThreadB();
        b.start();
 
        synchronized(b){
            try{
                System.out.println("Waiting for b to complete...");
                b.wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
 
            System.out.println("Total is: " + b.total);
        }
    }
}
 
class ThreadB extends Thread{
    int total;
    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<100 ; i++){
                total += i;
            }
            notify();
        }
    }
}

En el ejemplo anterior, un objeto, b, está sincronizado. b completa el cálculo antes de que el hilo principal emita su valor total.

Producción:

Waiting for b to complete...
Total is: 4950

Si b no está sincronizado como el siguiente código:

public class ThreadA {
	public static void main(String[] args) {
		ThreadB b = new ThreadB();
		b.start();
 
		System.out.println("Total is: " + b.total);
 
	}
}
 
class ThreadB extends Thread {
	int total;
 
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			total += i;
		}
	}
}

El resultado sería 0, 10, etc. Porque la suma no se termina antes de usarse.

3. notificar () y esperar () – ejemplo 2

El segundo ejemplo es más complejo, vea los comentarios.

import java.util.Vector;
 
class Producer extends Thread {
 
    static final int MAXQUEUE = 5;
    private Vector messages = new Vector();
 
    @Override
    public void run() {
        try {
            while (true) {
                putMessage();
                //sleep(5000);
            }
        } catch (InterruptedException e) {
        }
    }
 
    private synchronized void putMessage() throws InterruptedException {
        while (messages.size() == MAXQUEUE) {
            wait();
        }
        messages.addElement(new java.util.Date().toString());
        System.out.println("put message");
        notify();
        //Later, when the necessary event happens, the thread that is running it calls notify() from a block synchronized on the same object.
    }
 
    // Called by Consumer
    public synchronized String getMessage() throws InterruptedException {
        notify();
        while (messages.size() == 0) {
            wait();//By executing wait() from a synchronized block, a thread gives up its hold on the lock and goes to sleep.
        }
        String message = (String) messages.firstElement();
        messages.removeElement(message);
        return message;
    }
}
 
class Consumer extends Thread {
 
    Producer producer;
 
    Consumer(Producer p) {
        producer = p;
    }
 
    @Override
    public void run() {
        try {
            while (true) {
                String message = producer.getMessage();
                System.out.println("Got message: " + message);
                //sleep(200);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    public static void main(String args[]) {
        Producer producer = new Producer();
        producer.start();
        new Consumer(producer).start();
    }
}

Una posible secuencia de salida:

Got message: Fri Dec 02 21:37:21 EST 2011
put message
put message
put message
put message
put message
Got message: Fri Dec 02 21:37:21 EST 2011
Got message: Fri Dec 02 21:37:21 EST 2011
Got message: Fri Dec 02 21:37:21 EST 2011
Got message: Fri Dec 02 21:37:21 EST 2011
Got message: Fri Dec 02 21:37:21 EST 2011
put message
put message
put message
put message
put message
Got message: Fri Dec 02 21:37:21 EST 2011
Got message: Fri Dec 02 21:37:21 EST 2011
Got message: Fri Dec 02 21:37:21 EST 2011

Categorías
Concurrency

Java Thread: un código de ejemplo primordial

class A implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
 
class B implements Runnable {
 
    public void run() {
        new A().run();
        new Thread(new A(), "name_thread2").run();
        new Thread(new A(), "name_thread3").start();
    }
}
 
public class Main {
 
    public static void main(String[] args) {
        new Thread(new B(), "name_thread1").start();
    }
}

¿Cuál es la salida?

============================
name_thread1
name_thread1
name_thread3
============================ 

La diferencia entre «new Thread (new A (),» name_thread2 «). Run ();» y «new Thread (new A (),» name_thread3 «). Start ();» es que el método start () crea un nuevo hilo y ejecuta el método run en ese hilo. Si invoca el método run () directamente, el código del método run se ejecutará en el hilo actual. Esto explica por qué el código imprime dos líneas con el mismo nombre de hilo.

Categorías
Concurrency

Hilo de Java: Diagrama de estado

Este es un buen diagrama para mostrar el estado de los hilos de Java.

* Olvidé de dónde es, así que no sé a quién debo dar crédito.

Categorías
Concurrency Diagram

Monitores: la idea básica de la sincronización de Java

Si tomó un curso de sistema operativo en la universidad, es posible que recuerde que el monitor es un concepto importante de sincronización en los sistemas operativos. También se utiliza en la sincronización de Java. Esta publicación utiliza una analogía para explicar la idea básica de «monitor».

1. ¿Qué es un monitor?

Un monitor puede considerarse como un edificio que contiene una habitación especial. La sala especial puede ser ocupada por un solo cliente (hilo) a la vez. La sala suele contener algunos datos y códigos.

Si un cliente desea ocupar la habitación especial, primero debe ingresar al Pasillo (Conjunto de entrada) para esperar. El programador elegirá uno en función de algunos criterios (por ejemplo, FIFO). Si es suspendido por alguna razón, será enviado a la sala de espera y se programará que vuelva a ingresar a la sala especial más tarde. Como se muestra en el diagrama anterior, hay 3 habitaciones en este edificio.

En resumen, un monitor es una instalación que supervisa el acceso de los hilos a la sala especial. Asegura que solo un hilo pueda acceder a los datos o códigos protegidos.

2. ¿Cómo se implementa en Java?

En la máquina virtual Java, cada objeto y clase está asociado lógicamente con un monitor. Para implementar la capacidad de exclusión mutua de los monitores, se asocia un bloqueo (a veces llamado mutex) con cada objeto y clase. Esto se llama semáforo en los libros de sistemas operativos, mutex es un semáforo binario.

Si un hilo posee un bloqueo en algunos datos, entonces ningún otro puede obtener ese bloqueo hasta que el hilo que posee el bloqueo lo libere. No sería conveniente si tuviéramos que escribir un semáforo todo el tiempo cuando hacemos programación de subprocesos múltiples. Afortunadamente, no es necesario, ya que JVM lo hace por nosotros automáticamente.

Para reclamar una región de monitor, lo que significa que los datos no son accesibles por más de un hilo, Java proporciona declaraciones sincronizadas y métodos sincronizados. Una vez que el código está incrustado con la palabra clave sincronizada, es una región de monitorización. Los bloqueos son implementados en segundo plano automáticamente por JVM.

3. En el código de sincronización de Java, ¿qué parte es monitor?

Sabemos que cada objeto / clase está asociado con un Monitor. Creo que es bueno decir que cada objeto tiene un monitor, ya que cada objeto podría tener su propia sección crítica y ser capaz de monitorear la secuencia del hilo.

Para permitir la colaboración de diferentes subprocesos, Java proporciona wait () y notificar () para suspender un subproceso y para despertar otro subproceso que está esperando en el objeto, respectivamente. Además, existen otras 3 versiones:

wait(long timeout, int nanos)
wait(long timeout) notified by other threads or notified by timeout. 
notify(all)

Estos métodos solo se pueden invocar dentro de una instrucción sincronizada o un método sincronizado. La razón es que si un método no requiere exclusión mutua, no hay necesidad de monitorear o colaborar entre hilos, cada hilo puede acceder a ese método libremente.

A continuación, se muestran algunos ejemplos de códigos de sincronización.

Referencia:
1. Documento Java para objetos
2. Sincronización de subprocesos
3. Bloqueos y sincronización
4. notificar () vs notificar a todos ()

Categorías
Concurrency

Pregunta de entrevista: use Java Thread para hacer cálculos matemáticos

Este es un ejemplo para mostrar cómo usar join (). Pregunta de la entrevista: use Java multi-threading para calcular la expresión 1 * 2 / (1 + 2).

Solución:

Use un hilo para hacer la suma, un hilo para hacer la multiplicación y un hilo principal para hacer la división. Dado que no hay necesidad de comunicar datos entre subprocesos, solo es necesario considerar el orden de ejecución del subproceso.

En el hilo principal, deja adición y multiplicación unirse al hilo principal. El método join () se usa cuando queremos que el hilo principal espere hasta que finalicen los hilos que llaman a join (). En este caso, queremos completar la suma y la multiplicación primero y luego hacer la división.

class Add extends Thread {
	int value;
 
	public void run() {
		value = 1 + 2;
	}
}
 
class Mul extends Thread {
	int value;
 
	public void run() {
		value = 1 * 2;
	}
}
 
public class Main{
	public static void main(String[] args){
		Add t1 = new Add();
		Mul t2 = new Mul();
 
		t1.start();
		t2.start();
 
		try {
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
 
		double n = ((double)t2.value/t1.value);
 
		System.out.println(n);		
	}
}

Categorías
Concurrency linux

Java Runtime.exec () Linux Pipe

Si desea utilizar las ventajas de las tuberías de Linux, puede hacerlo en Java.

Es posible que desee hacer lo siguiente, que es incorrecto.

String cmd = "ls"
Process p = Runtime.getRuntime().exec(cmd);

La forma correcta de ejecutar el comando de shell es la siguiente:

String[] cmd = {
"/bin/sh",
"-c",
"ls"
};
 
Process p = Runtime.getRuntime().exec(cmd);
InputStream i = p.getInputStream();
byte[] b = new byte[16];
i.read(b, 0, b.length); 
System.out.println(new String(b));

En este ejemplo, el programa simplemente imprime las cadenas. Pero en el uso real, puede alimentarlo con algún proceso complicado.

Categorías
Concurrency Interview

¿Cómo hacer que un método sea seguro para subprocesos en Java?

Pregunta de la entrevista:

¿El siguiente método es seguro para subprocesos? ¿Cómo hacerlo seguro para subprocesos?

class MyCounter {
	private static int counter = 0;
 
	public static int getCount() {
		return counter++;
	}
}

Esta publicación explica una pregunta de entrevista general que ha sido formulada por Google y muchas empresas. Es de bajo nivel y no se trata de cómo diseñar un programa concurrente.

En primer lugar, la respuesta es NO. El método no es seguro para subprocesos, porque el contador ++ La operación no es atómica, lo que significa que consta de más de una operación atómica. En este caso, uno accede al valor y el otro aumenta el valor en uno.

Cuando el subproceso 1 accede al método en t1, es posible que el subproceso 2 no se complete con el método. Por tanto, el valor devuelto al hilo 1 es el valor que no se ha aumentado.

Hacer un método seguro para subprocesos – Método 1

Agregar sincronizado a este método lo hará seguro para subprocesos. Cuándo sincronizado se agrega a un método estático, el Clase objeto es el objeto que está bloqueado.

¿Marcarlo está lo suficientemente sincronizado? La respuesta es sí.

class MyCounter {
	private static int counter = 0;
 
	public static synchronized int getCount() {
		return counter++;
	}
}

Si el método no es estático, agregue sincronizado La palabra clave sincronizará la instancia de la clase, no la Clase objeto.

Hacer un método seguro para subprocesos – Método 2

En este contraejemplo en particular, podemos hacer contar ++ atomic utilizando AtomicInteger del paquete «java.util.concurrent.atomic».

import java.util.concurrent.atomic.AtomicInteger;
 
public class MyCounter {
	private static AtomicInteger counter = new AtomicInteger(0);
 
	public static int getCount() {
		return counter.getAndIncrement();
	}
}

Algunos otros datos útiles sobre la seguridad para subprocesos

Las variables locales son seguras para subprocesos en Java.

Cada hilo tiene su propia pila. Dos subprocesos diferentes nunca comparten la misma pila. A todas las variables locales definidas en un método se les asignará memoria en la pila. Tan pronto como el hilo actual complete la ejecución del método, se eliminará el marco de pila.