danilo.pianini@unibo.itgianluca.aguzzi@unibo.itangelo.filaseta@unibo.itCompiled on: 2025-12-05 — versione stampabile
Programmazione asincrona: esecuzione di operazioni che possono completarsi in momenti diversi senza bloccare il flusso principale del programma.
Programmazione concorrente: esecuzione di più operazioni in parallelo, spesso utilizzando thread o processi separati.
Thread: Un thread è un’unità di esecuzione all’interno di un processo. Ogni thread ha il proprio stack di esecuzione, ma condivide lo spazio di memoria del processo con altri thread.
Un thread è rappresentato dalla classe Thread, caratterizzata dal metodo
run, che definisce il comportamento (attività) del thread
class Thread {
public void run();
...
}
Nota: In generale è sconsigliato estendere direttamente la classe Thread
public class Clock implements Runnable {
private String name;
private int amount;
public Clock(String name, int amount) {
this.name = name;
this.amount = amount;
}
@Override
public void run() {
while(true) {
System.out.println("Clock " + name + ": " + System.currentTimeMillis());
try {
Thread.sleep(this.amount); // Pausa di 1 secondo
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
void main(String[] args) {
Clock clock1 = new Clock("A", 500);
Thread thread1 = new Thread(clock1);
thread1.start();
Clock clock2 = new Clock("B", 1000);
Thread thread2 = new Thread(clock2);
thread2.start();
}
Un possibile output sarà:
Clock A: 1700000000000
Clock A: 1700000000500
Clock B: 1700000001000
Clock A: 1700000001500
start() non dà garanzie sull’ordine di esecuzione dei thread! (quindi thread1 potrebbe partire dopo thread2)void main() throws InterruptedException {
Thread t1 = new Thread(() -> System.out.println("Thread 1"));
Thread t2 = new Thread(() -> System.out.println("Thread 2"));
t1.start();
t2.start();
System.out.println("after start");
}
Thread 1
Thread 2
after start
Thread 2
after start
Thread 1
join() della classe Thread!void main() throws InterruptedException {
Thread t1 = new Thread(() -> System.out.println("Thread 1"));
Thread t2 = new Thread(() -> System.out.println("Thread 2"));
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("after join");
System.out.println("Tutti i thread hanno finito!");
}
join() blocca il thread chiamante finché il thread su cui viene chiamato non terminaclass Counter {
private int counter = 0;
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
public static Runnable incrementFor(int amount, Counter counter) {
return () -> {
for(int i = 0; i < amount; i++) {
counter.increment();
}
};
}
}
void main() throws InterruptedException {
var shared = new Counter();
var thread1 = new Thread(Counter.incrementFor(1000, shared));
var thread2 =new Thread(Counter.incrementFor(1000, shared));
thread1.start();
thread2.start();
thread2.join();
thread1.join();
System.out.println("Final counter value: " + shared.getCounter());
}
Final counter value: 2000counter viene letta e scritta da due thread contemporaneamente, causando una corsa criticaUna corsa critica si verifica quando due o più thread accedono contemporaneamente a una risorsa condivisa (ad esempio, una variabile) e almeno uno di essi modifica la risorsa. Questo può portare a risultati imprevedibili e incoerenti.
counter, lo incrementano e lo riscrivono. Se entrambi leggono il valore prima che uno di loro lo riscriva, perderemo un incremento.increment() (in mutua esclusione)Un monitor è una struttura di sincronizzazione che consente a un solo thread alla volta di eseguire un blocco di codice protetto. In Java, ogni oggetto ha un monitor associato, e i metodi sincronizzati utilizzano questo monitor per garantire l’accesso esclusivo.
Counter sincronizzato:public class SynchronizedCounter {
private int counter = 0;
public synchronized void increment() {
counter++;
}
public synchronized int getCounter() {
return counter;
}
static Runnable incrementFor(int amount) {
return () -> {
for(int i = 0; i < amount; i++) {
increment();
}
};
}
}
SynchronizedCounter nel nostro esempio:static void main(String[] args) throws InterruptedException {
var shared = new SynchronizedCounter();
var thread1 = new Thread(SynchronizedCounter.incrementFor(1000, shared));
var thread2 = new Thread(SynchronizedCounter.incrementFor(1000, shared));
thread1.start();
thread2.start();
thread2.join();
thread1.join();
System.out.println("Final counter value: " + shared.getCounter());
}
SynchronizedCounter al posto di Counter, i due thread non potranno eseguire increment() contemporaneamente, evitando la corsa criticaFinal counter value: 2000wait(), notify() e notifyAll()
wait(): sospende l’esecuzione del thread corrente fino a quando un altro thread non chiamanotify()onotifyAll()sullo stesso oggetto.notify(): risveglia un thread in attesa sul monitor dell’oggetto.notifyAll(): risveglia tutti i thread in attesa sul monitor dell’oggetto.
wait() e notify() per gestire questa comunicazioneclass UnboundedBuffer<T> {
private Queue<T> buffer = new LinkedList<>();
public synchronized void put(T item) {
buffer.add(item);
notify(); // Notifica un consumatore in attesa
}
public synchronized T take() throws InterruptedException {
while (buffer.isEmpty()) {
wait(); // Aspetta che ci sia un elemento
}
return buffer.remove();
}
}
UnboundedBuffer<Integer> buffer = new UnboundedBuffer<>();
// put 10 elements before
for (int i = 0; i < 10; i++) {
buffer.put(i);
}
Thread producer = new Thread(() -> {
for (int i = 10; i < 20; i++) {
buffer.put(i);
System.out.println("Produced: " + i);
}
});
Thread consumer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
Object item = buffer.take();
System.out.println("Consumed: " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
run() fino a quando non terminarun(), il thread non terminerà mai!class StopSignal {
private boolean stop = false;
public synchronized void requestStop() {
stop = true;
}
public synchronized boolean shouldStop() {
return stop;
}
}
class StoppableTask implements Runnable {
private StopSignal stopSignal;
public StoppableTask() {
this.stopSignal = new StopSignal();
}
@Override
public void run() {
while (!stopSignal.shouldStop()) {
// Esegui il lavoro
System.out.println("Working...");
}
}
public void kill() {
stopSignal.requestStop();
}
}
kill()
Esempio:StoppableTask task = new StoppableTask();
new Thread(task).start();
thread.start();
task.kill(); // Chiede al thread di fermarsi
public class UnresponsiveUI extends Application {
@Override
public void start(Stage primaryStage) {
Button btn = new Button("Calcola");
TextField result = new TextField();
btn.setOnAction(event -> {
long sum = 0;
for (long i = 0; i < 1_000_000_000_0L; i++) { sum ++; }
result.setText("Somma: " + sum);
});
VBox root = new VBox();
root.getChildren().addAll(btn, result);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Esempio JavaFX");
primaryStage.setScene(scene);
primaryStage.show();
}
}
public class ResponsiveUI extends Application {
@Override
public void start(Stage primaryStage) {
Button btn = new Button("Calcola");
TextField result = new TextField();
btn.setOnAction(event -> {
new Thread(() -> {
long sum = 0;
for (long i = 0; i < 1_000_000_000_0L; i++) { sum ++; }
final long finalSum = sum;
}).start();
});
VBox root = new VBox();
root.getChildren().addAll(btn, result);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Esempio JavaFX");
primaryStage.setScene(scene);
primaryStage.show();
}
}
Platform.runLater(Runnable r) per eseguire codice sul JavaFX Application ThreadPlatform.runLater(() -> {
result.setText("Somma: " + finalSum);
});
runLater verrà eseguito sul JavaFX Application Thread, evitando problemi di concorrenza con l’interfaccia graficarunLater, altrimenti si bloccherà l’interfaccia grafica!while (gameIsRunning) {
inputHandling(); // Gestisce l'input dell'utente
updateGameLogic(); // Aggiorna la logica di gioco
renderGraphics(); // Esegue il rendering grafico
sleepUntilNextFrame(); // Attende fino al prossimo frame
}
public abstract class GameLoop<I, O> implements Runnable {
@Override
public void run() {
while(true) {
var start = System.currentTimeMillis();
processInput();
updateGame();
waitForNextFrame(start);
}
}
private void processInput() { ... }
private void updateGame() { ... }
private void waitForNextFrame(long startTime) { ... }
protected abstract void processInput(I input);
protected abstract O logic(float deltaTimeMillis);
}
public class SynchValue<V> {
private V value = null;
public synchronized void setValue(V value) {
this.value = value;
this.notifyAll();
}
public synchronized V getValue() throws InterruptedException {
while (value == null) {
wait();
}
var result = this.value;
this.value = null;
return result;
}
public synchronized Optional<V> tryGetValue() {
if (value == null) {
return Optional.empty();
}
var result = this.value;
this.value = null;
return Optional.of(result);
}
}
class GameLoop<I, O> implements Runnable {
private SynchValue<I> inputBuffer = new SynchValue<>();
private SynchValue<O> outputBuffer = new SynchValue<>();
...
private void processInput() {
var input = this.inputBuffer.tryGetValue(); // non bloccante
input.ifPresent(this::processInput);
}
private void updateGame() {
var result = logic(expectedFrameTimeMillis);
this.outputBuffer.setValue(result);
}
public void sendInput(I input) {
this.inputBuffer.setValue(input);
}
}
danilo.pianini@unibo.itgianluca.aguzzi@unibo.itangelo.filaseta@unibo.itCompiled on: 2025-12-05 — versione stampabile