<div class="page">
<div class="cover text-center">
<img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo">
# Progamació de fils
<div class="text-end fit-content ms-auto my-3 mt-auto pt-3">
<p><strong>Autor:</strong> Joan Puigcerver Ibáñez</p>
<p><strong>Correu electrònic:</strong> j.puigcerveribanez@edu.gva.es</p>
<p><strong>Curs:</strong> 2024/2025</p>
</div>
<div>
<p class="fw-bold mb-0">Llicència: BY-NC-SA</p>
<p class="d-none d-md-block">(Reconeixement - No Comercial - Compartir Igual)</p>
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ca" target="_blank">
<img class="mx-auto" src="/itb/images/license.png" alt="Licence"/>
</a>
</div><!--license-->
</div><!--cover-->
</div><!--page-->
{:toc}
## Conceptes bàsics
__Fil:__ Un __fil__ (_thread_ en anglés) és la unitat més xicoteta de instruccions programades
que són gestionades pel sistema operatiu. La implementació de fils i processos és diferent
per cada sistema operatiu, però en la majoria dels casos, __un fil és una part d'un procés__.
{.center}{height=300}
Els fils són gestionats pel sistema operatiu, però més especificament pel __planificador de tasques (_sheduler_)__.
Els distints fils d'un procés es poden executar de manera __councurrent__, compartint recursos del procé (com la memòria),
cosa que no ocurreix entre diferents processos.
##### Avantatges de la multitasca davant de la multiprogramació
- __Major capacitat de resposta__: pel fet que hi pot haver un fil atenent peticions mentre un altre
realitza una altra tasca més llarga. S'usa a la programació de serveis als servidors, un fil
s'encarrega de rebre peticions i per cada petició s'obre un altre fil per atendre-la.
- __Compartició de recursos__, ja que tots els fils d'un procés tenen accés als recursos del procés,
per la qual cosa no cal cap mecanisme addicional. El que s'haurà d'evitar són els problemes
derivats que diversos fils accedeixin alhora a un recurs, per exemple, la memòria, així caldrà
prestar més atenció a la sincronitzacióentre fils.
- Com que tots els fils d'un procés utilitzen el mateix espai de memòria, per crear nous fils no cal
reservar memòria. Parlant d'ús de memòria i recursos és __més barat__ crear fils que processos.
- En sistemes amb diversos nuclis reals s'aconsegueix un __paral·lelisme real__ en l'execució de fils.
## Recurusos compartits per fils
Un fils és molt paregut a un process, per tant, necessiten pràcticament les mateixes dades per funcionar.
L'única diferencia és que hi ha alguns recursos que estan compartits entre tots els fils d'un procés,
i d'altres, que cada fil té el seu.
Cada fil té el seu propi comprador de programa, seus registres i la seua pila. Dins d'un mateix procés,
tots els fils comparteixen el codi font, les dades i el recursos.
{.center}
## Estats d'un fil
Els fils, com els processos, poden canviar d'estat durant l'execució.
El comportament dependrà de l'estat:
- __Nou__: el fil es troba preparat per ser executat però encara no s'ha cridat. Els fils s'inicien
en la creació del procés, però no comencen fins que el procés ho indique.
- __A punt__: El fil no s'està executant però està preparat, per fer-ho.
- __Executable__ (runnable): El fil està preparat per executar-se o fins i tot en execució. No es
pot saber si s'està executant o no, perquè el maquinari no informa d'aquesta situació.
Simplificant, es considera que tots els fils d'un procés s'executen de manera paral·lela.
- __Bloquejat__: el fil està bloquejat, per exemple, esperant una operació E/S o una sincronització.
- __Acabat__: El fil ha finalitzat, però no ha alliberat recursos, ja que no li pertanyen a ell sinó al procés
que l'ha creat. Poden acabar per si mateix o perquè el procés que el ha creat el finalitza.
## Gestió de fils en Java
De manera general, un fil és un procés que s'està executant en un moment determinat a la CPU. Hi
ha programes que per la senzillesa només utilitzen un fil d'execució, mentre que altres programes
més complexos utilitzen diversos fils d'execució. Als fils se'ls anomena processos lleugers, en
contraposició als processos anomenats processos pesats.
Un exemple de la programació multifil és el d'un programari que utilitza una interfície gràfica.
Un fil s'encarrega de gestionar l'interfície gràfica i les peticions de l'usuari, i els altres fils
s'encarreguen de realitzar les tasques en el background. Si no es fa d'aquesta manera,
l'interfície gràfica es queda bloquejada fins que acabe la tasca.
A Java, els fils es gestionen mitjançant la classe __Thread__.
### Creació i llancament de fils
Quan un programa s'executa, es crea un procés que té un fil d'execució principal. En aquest fil d'execució,
es poden crear nous fils per executar diferents tasques. Els nous fils no cal que executen el mateix codi
que el fil principal.
A Java, per crear un nou fil cal crear un nou objecte, que ha de complir algun dels següents requisits:
- Extendre la classe __Thread__: `extends Thread`.
- Implementant la interfície __Runnable__: `implements Runnable`.
En qualsevol de les dues opecions, caldrà crear una classe nova: `Class`. Aquesta classe ha d'implementar
el mètode __`public void run()`__, que contindrà el codi que executarà el fil.
En qualsevol de les dues opecions, caldrà crear una instància de la classe __Thread__ per poder
executar el fil.
#### Exemple: HelloRunnable
- <a href="/itb/DAM-PSP/files/ud2/examples/runnable/HelloRunnable.java" download="HelloRunnable.java">HelloRunnable.java</a>
```java
package ud2.examples.runnable;
import java.util.concurrent.ThreadLocalRandom;
class HelloRunnable implements Runnable {
public void run(){
for(int i = 0; i < 5; i++) {
int sleepTime = ThreadLocalRandom.current().nextInt(500, 1000);
//Thread.sleep(sleepTime);
System.out.printf("El fil %s et saluda per %d vegada.\n",
Thread.currentThread().getName(), i
);
}
}
}
```
- <a href="/itb/DAM-PSP/files/ud2/examples/runnable/StartRunnables.java" download="StartRunnables.java">StartRunnables.java</a>
```java
package ud2.examples.runnable;
import java.util.concurrent.ThreadLocalRandom;
public class StartRunnables {
public static void main(String[] args) {
HelloRunnable rc = new HelloRunnable();
Thread thread1 = new Thread(rc);
thread1.setName("Fil1");
Thread thread2 = new Thread(rc);
thread2.setName("Fil2");
Thread thread3 = new Thread(rc);
thread3.setName("Fil3");
thread1.start();
thread2.start();
thread3.start();
for(int i = 0; i < 5; i++) {
int sleepTime = ThreadLocalRandom.current().nextInt(500, 1000);
//Thread.sleep(sleepTime);
System.out.printf("El fil principal et saluda per %d vegada.\n", i );
}
}
}
```
#### Exemple: HelloThread
- <a href="/itb/DAM-PSP/files/ud2/examples/thread/HelloThread.java" download="HelloThread.java">HelloThread.java</a>
```java
package ud2.examples.thread;
import java.util.concurrent.ThreadLocalRandom;
class HelloThread extends Thread {
@Override
public void run(){
try {
for(int i = 0; i < 5; i++) {
int sleepTime = ThreadLocalRandom.current().nextInt(500, 1000);
Thread.sleep(sleepTime);
System.out.printf("El fil %s et saluda per %d vegada.\n",
Thread.currentThread().getName(), i
);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
```
- <a href="/itb/DAM-PSP/files/ud2/examples/thread/StartThreads.java" download="StartThreads.java">StartThreads.java</a>
```java
package ud2.examples.thread;
public class StartThreads {
public static void main(String[] args) {
HelloThread thread1 = new HelloThread();
thread1.setName("Fil1");
HelloThread thread2 = new HelloThread();
thread2.setName("Fil2");
HelloThread thread3 = new HelloThread();
thread3.setName("Fil3");
thread1.start();
thread2.start();
thread3.start();
for(int i = 0; i < 5; i++) {
System.out.printf("El fil principal et saluda per %d vegada.\n", i);
}
}
}
```
### Pausar l'execució d'un fil: `sleep`
Es pot pausar l'execució de qualsevol fil (també del principal) durant un temps determinat.
Per fer-ho, podem utilitzar el mètode [`Thread sleep(long millis)`](https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#sleep(long)),
que pausa el Thread indicat tans milisegons com s'indica.
Aquest mètode llança una excepció `InterruptedException`, que cal gestionar.
```java
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() +" interromput.");
}
```
### Parar l'execució d'un fil: `interrupt`
El fil que ha creat un altre fil secondari, pot decidir acabar amb l'execució del segon. Aquestà acció s'anomena __interrompre l'execució
d'un fil__ i es pot portar a terme mitjançant el mètode [`Thread interrupt()`](https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#sleep(long)),
que acaba la seua execució.
Si el fil interromput està en un mètode `sleep` o `join`, es llançarà l'excepció `InterruptedException`.
#### Exemple: InterruptedThread
- <a href="/itb/DAM-PSP/files/ud2/examples/interrupt/InterruptedThread.java" download="InterruptedThread.java">InterruptedThread.java</a>
```java
package ud2.examples.interrupt;
class InterruptedThread extends Thread {
@Override
public void run(){
try {
for(int i = 0; i < 1000; i++) {
System.out.printf("El fil %s et saluda per %d vegada.\n",
Thread.currentThread().getName(), i
);
Thread.sleep(100);
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() +" interromput.");
}
}
}
```
- <a href="/itb/DAM-PSP/files/ud2/examples/interrupt/StartInterruptedThread.java" download="StartInterruptedThread.java">StartInterruptedThread.java</a>
```java
package ud2.examples.interrupt;
public class StartInterruptedThread {
public static void main(String[] args) {
Thread.currentThread().setName("Fil principal");
InterruptedThread thread1 = new InterruptedThread();
thread1.setName("Fil1");
InterruptedThread thread2 = new InterruptedThread();
thread2.setName("Fil2");
InterruptedThread thread3 = new InterruptedThread();
thread3.setName("Fil3");
thread1.start();
thread2.start();
thread3.start();
try {
Thread.sleep(500);
thread1.interrupt();
Thread.sleep(500);
thread2.interrupt();
Thread.sleep(500);
thread3.interrupt();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() +" interromput.");
}
}
}
```
### Esperar a que un fil acabe: `join`
Igual que amb els processos, es pot forçar a què el fil pare espere que el fil fill finalitze.
Per fer-ho utilitzarem el mètode [`Thread join(long millis)`](https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join(long)),
que espera a que aquest `Thread` acabe per continuar.
El paràmetre `long millis` indica el temps màxim per esperar que el fill acabe. Si supera aquest temps, el fil que
estava esprant continuarà la seua execució. Si s'invoca amb un __0__ o sense parametre, esperarà indefinidament.
Aquest mètode llança una excepció `InterruptedException`, que cal gestionar.
```java
Thread t1 = new SleepThread(5000);
try {
t1.start(); // Llança el fil
t1.join(); // El fil principal esperarà a que acabe el fil f1
System.out.println("El fil1 ha acabat.");
} catch (InterruptedException e) {
System.out.println(t1.getName() +" interromput.");
}
```
#### Exemple: PowerThread
Aquest exemple crea diferents `Thread`, per calcular diferènts potències.
Una vegada totes les potències han segut calculades, el fil principal fa la suma de tots els valors.
Utilitzem el mètode `join` per esperar que els `PowerThread` acaben. Cada `PowerThread` espera _100ms_ en
cada operació realitzada.
- <a href="/itb/DAM-PSP/files/ud2/examples/join/PowerThread.java" download="PowerThread.java">PowerThread.java</a>
```java
package ud2.examples.join;
public class PowerThread extends Thread {
private final int base;
private final int exponent;
private int power;
public PowerThread(int base, int exponent){
super();
this.base = base;
this.exponent = exponent;
this.power = 0;
this.setName(String.format("FIL-%d^%d", base, exponent));
}
public int getPower(){
return this.power;
}
@Override
public void run(){
try {
int result = 1;
for (int i = 0; i < exponent; i++) {
result *= base;
Thread.sleep(100);
System.out.printf("%s: %d ^ %d = %d\n",
Thread.currentThread().getName(),
base, i + 1, result
);
}
this.power = result;
} catch(InterruptedException e){
return;
}
}
}
```
- <a href="/itb/DAM-PSP/files/ud2/examples/join/RunPowerThreads.java" download="PowerThread.java">PowerThread.java</a>
```java
package ud2.examples.join;
import java.util.ArrayList;
public class RunPowerThreads extends Thread {
public static void main(String[] args) {
ArrayList<PowerThread> powerThreads = new ArrayList<>();
powerThreads.add(new PowerThread(2, 3));
powerThreads.add(new PowerThread(3, 5));
powerThreads.add(new PowerThread(2, 10));
powerThreads.add(new PowerThread(5, 5));
for (PowerThread t : powerThreads) {
t.start(); // Iniciem tots els threads
}
for (PowerThread t : powerThreads) {
try {
t.join(); // Esperem a que tots els threads acaben
System.out.printf("Resultat del fil %s: %d\n", t.getName(), t.getPower());
} catch(Exception e){
e.printStackTrace();
}
}
int suma = 0;
for (PowerThread t : powerThreads) {
suma += t.getPower();
}
System.out.printf("La suma de les potències és: %d\n", suma);
}
}
```