Concurrencia en la JVM¶
En la Máquina Virtual de Java (JVM):
- Proceso: Es una instancia de un sistema operativo, la cual puede tener una o más tareas paralelas. Los procesos se encuentran aislados en memoria de otros procesos.
- Hilo: Es una unidad de ejecución que ejecuta una o más instrucciones secuenciales. Un proceso puede contener uno o más hilos. Los hilos comparten la memoria del proceso, sin embargo, tienen su propia pila de ejecución.
- Existe un hilo denominado main o hilo principal de ejecución.
- Para esto contamos con la clase
Threadque nos proporciona la posibilidad de ejecutar instrucciones en hilos.
Creación de Hilos¶
def thread(body: => Unit): Thread = {
val t = new Thread {
override def run() = body
}
t.start()
t
}
Al ejecutar thread(...) se lanza un nuevo hilo que ejecuta el proceso.
def log(msg: String): Unit = {
println(s"${Thread.currentThread.getName}: $msg")
}
Ejemplo de Ejecución Concurrente¶
def main(arr:Array[String]): Unit = {
log("Desde main")
thread(log("Desde thread 1"))
thread(log("Desde thread 2"))
thread(log("Desde thread 3"))
log("Fin de main")
}
Resultados de ejecución:
main: Desde main
Thread-1: Desde thread 1
Thread-2: Desde thread 2
Thread-3: Desde thread 3
main: Fin de main
# Otra ejecución posible:
main: Desde main
Thread-1: Desde thread 1
Thread-2: Desde thread 2
main: Fin de main
Thread-3: Desde thread 3
Observaciones Clave¶
- El hilo principal del programa se llama main
- Cada vez que se lanza un hilo se nombra como Thread-num donde num es el número del hilo
- Al ejecutar varias veces el programa, el orden de los resultados puede ser diferente, esto hace que la ejecución de los hilos no sea determinista
Atomicidad¶
En ocasiones necesitamos asegurar que una secuencia de instrucciones se ejecuten en un orden dado, es decir, no puede haber lugar a intercalación.
object Ejemplo2 {
var id = 0
def getId():Int = {
val newId = id + 1
Thread.sleep(1000)
id = newId
newId
}
def thread(body: => Unit): Thread = {
val t = new Thread {
override def run() = body
}
t.start()
t
}
def main(arr:Array[String]):Unit = {
thread(println(getId()))
thread(println(getId()))
thread(println(getId()))
println(id)
}
}
Problema de sincronización:
0
1
1
1
Se observa que debido a la espera de 1 segundo, todos los hilos ven a la variable id con el valor 0.
Solución con Sincronización¶
Para arreglar esto, los lenguajes de programación incluyen directivas de sincronización. En Scala contamos con la directiva synchronized:
object Ejemplo2 {
var id = 0
def getId():Int = synchronized {
val newId = id + 1
Thread.sleep(1000)
id = newId
newId
}
def thread(body: => Unit): Thread = {
val t = new Thread {
override def run() = body
}
t.start()
t
}
def main(arr:Array[String]):Unit = {
thread(println(getId()))
thread(println(getId()))
thread(println(getId()))
println("linea 22 " + id)
}
}
Resultado con sincronización:
linea 22 0
1
2
3
Uso de Join para Sincronización¶
Ahora podemos usar join para esperar que los hilos terminen, es decir, se unan al hilo principal de ejecución:
object Ejemplo2 {
var id = 0
def getId():Int = synchronized {
val newId = id + 1
Thread.sleep(1000)
id = newId
newId
}
def thread(body: => Unit): Thread = {
val t = new Thread {
override def run() = body
}
t.start()
t
}
def main(arr:Array[String]):Unit = {
val t1 = thread(println(getId()))
val t2 = thread(println(getId()))
val t3 = thread(println(getId()))
t1.join()
t2.join()
t3.join()
println("linea 22 " + id)
}
}
Resultado con join:
1
2
3
linea 22 3
Tabla de Resumen¶
| Concepto | Definición | Características | Ejemplo |
|---|---|---|---|
| Proceso | Instancia de sistema operativo con tareas paralelas | Aislamiento de memoria entre procesos, un proceso puede tener uno más hilos | - |
| Hilo | Unidad de ejecución de instrucciones secuenciales | Comparte memoria del proceso, tiene pila propia | Thread-1, Thread-2 |
| Hilo Main | Hilo principal de ejecución en la JVM | Punto de entrada del programa | main |
| No Determinismo | Ejecución no predecible de hilos | Orden de ejecución puede variar entre ejecuciones | Diferente orden de salida |
| Atomicidad | Ejecución indivisible de secuencias de instrucciones | Previene intercalación entre hilos | Problema con variable id |
| Race Condition | Condición de carrera cuando múltiples hilos acceden datos compartidos | Resultado depende del timing de ejecución | getId() sin synchronized |
| Synchronized | Directiva de sincronización en Scala | Garantiza acceso exclusivo a secciones críticas | synchronized { ... } |
| Join | Método para esperar que un hilo termine | Sincroniza la finalización de hilos | t1.join(), t2.join() |