La concorrenza in Java package java.util.concurrent Antonio Furone Coda Bloccante La classe LinkedTransferQueue è stata esplicitamente disegnata per dare supporto a problemi di tipo produttore/consumatore. Il trasferimento di elementi tra produttore e consumatore può avvenire anche con la modalità bloccante, nella quale il produttore rimane in attesa durante il trasferimento di nuovo elemento nella coda (put) fino a quando il consumatore non ha effettivamente consumato il precedente elemento. Di seguito viene riportato l esempio di un Produttore che inserisce n elementi in una coda con modalità bloccante (metodo transfer) e un ulteriore elemento in modalità non bloccante (metodo trytransfer), quindi eseguito solo se il passaggio al consumatore può avvenire immediatamente o entro un timeout specificato. package it.af; import java.util.concurrent.linkedtransferqueue; import java.util.concurrent.transferqueue; import java.util.logging.logger; public class LinkedTransferQueueSample { private static final Logger LOGGER = Logger.getLogger(LinkedTransferQueueSample.class.getName()); private static final int NUM_ELEMENTS = 5; // classe Producer public static class Producer implements Runnable { TransferQueue<String> queue = null; public Producer(TransferQueue<String> transfer) { this.queue = transfer; for (int i = 0; i < NUM_ELEMENTS; i++) { LOGGER.info("Thread" + Thread.currentThread().getId()+" Prodotto: Elemento " + i); queue.transfer("elemento " + i); LOGGER.info("Exception:" +e.getmessage()); Thread.sleep(100); LOGGER.info("Ignore exception:" +e.getmessage()); LOGGER.info("Thread" + Thread.currentThread().getId()+" Modalita' non bloccata "); queue.trytransfer("non bloccato"); LOGGER.info("Thread" + Thread.currentThread().getId()+" Trasferito elemento con modalita' non bloccata!");
// classe Consumer public static class Consumer implements Runnable { TransferQueue<String> queue = null; public Consumer(TransferQueue<String> transfer) { this.queue = transfer; for (int ind = 0; ind < NUM_ELEMENTS+1; ind++) { String element = queue.take(); LOGGER.info("Thread"+ Thread.currentThread().getId()+" Consumato: "+ element); LOGGER.info("Exception:" +e.getmessage()); // main public static void main(string[] args) throws InterruptedException { TransferQueue<String> queue =new LinkedTransferQueue<String>(); Producer aproducer =new Producer(queue); Thread treadproducer =new Thread(aProducer); treadproducer.start(); try { Thread.sleep(1_000); // attesa 1 sec LOGGER.info("Exception:" + e.getmessage()); Consumer aconsumer = new Consumer(queue); Thread treadconsumer = new Thread(aConsumer); treadconsumer.start(); Sincronizzazione di Task La classe Phaser (fasatore) è stata introdotta per consentire la realizzazione di complessi algoritmi paralleli che richiedono, ai thread coinvolti, di sincronizzarsi in diversi punti. La registrazione dei partecipanti (tipicamente Thread) al Phaser, avviene dinamicamente attraverso i metodi register() e bulkregister(int). Simmetricamente, i partecipanti possono rimuovere la propria registrazione in qualsiasi momento utilizzando, ad esempio, il metodo arriveandderegister(), mentre il metodo arriveandawaitadvance() segnala l arrivo (completamento di una fase) al Phaser da parte di un partecipante e l attesa dell arrivo degli altri partecipanti per eventualmente procedere. Ogni istanza del Phaser ha associato un numero di fase (intero) che viene incrementato ogni qualvolta tutti i soggetti interessati hanno comunicato il loro arrivo al fasatore. La sincronizzazione, come è facile intuire, prevede due fasi: arrivo e attesa. L arrivo viene notificato dai partecipanti attraverso i metodi arrive() e arriveandderegister(), i quali restituisco il numero di fase attiva. Nel momento in cui arriva l ultimo dei partecipanti alla fase, viene eseguito il metodo overridable onadvace(int,int), nel quale è possibile innestare una qualche azione di preavanzamento. L attesa da parte dei task viene implementata attraverso il metodo
awaitadvance(int), il quale termina quando il Phaser avanza e dove il parametro passato è l identificativo della fase (quello restituito ad esempio dal metodo arrive()). Un Phaser può entrare anche in uno stato di terminazione (il metodo isterminated() restituisce true) e quando ciò avviene, tutti i metodi di sincronizzazione terminano sbloccando eventuali thread in attesa e segnalando questo evento attraverso la restituzione di valori negativi. Di seguito viene riportato un esempio di utilizzo della classe Phaser con un gruppo di Thread che si sincronizzano su tre fasi e una classe (Monitor) che consente di monitorare lo stato del Phaser. package it.af; import java.util.concurrent.phaser; import java.util.concurrent.threadlocalrandom; import java.util.logging.logger; public class PhaserSample { private static final Logger LOGGER = Logger.getLogger(PhaserSample.class.getName()); private static final int NUM_THREADS = 10; private static String getcurrentthreadid() { String threadid = Thread.currentThread().getId() + ""; if (Thread.currentThread().getId() < 10) {threadid = "0" + threadid; return threadid; private static void dump(phaser aphaser, String txt) { LOGGER.info(txt+"\t"+" Thread :" + getcurrentthreadid()+" Fase: " + aphaser.getphase() + " reg.:"+ aphaser.getregisteredparties() + " arrivati:"+aphaser.getarrivedparties() + " non arrivati:"+aphaser.getunarrivedparties()); // classe Monitor static class Monitor implements Runnable { private final Phaser aphaser; public Monitor(Phaser givenphaser) { aphaser = givenphaser; while ( (aphaser!= null) &&(!aphaser.isterminated()) ) { PhaserSample.dump(aPhaser,"Monitor Start..."); Thread.sleep(80); LOGGER.info("Ignore exception:" + e.getmessage()); LOGGER.info("...Monitor Shutdown"); // MonitorPhaser static class Task implements Runnable { private final Phaser aphaser; public Task(Phaser givenphaser) { aphaser = givenphaser; aphaser.register();
// run execphase("fase A"); execphase("fase B"); execphase("fase C"); int newarrivalphase =aphaser.arriveandderegister(); dump(aphaser,"task Deregister. returned:"+ newarrivalphase); private void execphase(string phasename) { boolean last = false; dosomework(); String txt = phasename+" completata!"; if (aphaser.getunarrivedparties() == 1) { txt =" Ultimo thread arrivato!"; last = true; dump(aphaser, phasename + txt); int arrivalphase = aphaser.arrive(); dump(aphaser,phasename + " Await to advance..."); aphaser.awaitadvance(arrivalphase); dump(aphaser,phasename + " Advance next phase."); if (last) LOGGER.info("------------------------------------ "); // execnextphase private void dosomework() { // end Task LOGGER.info("Working...\t"+" Thread :" + getcurrentthreadid()); Thread.sleep( ThreadLocalRandom.current().nextInt(10,400)); LOGGER.info("Exception:" + e.getmessage()); public static void main(string[] args) { Phaser phaser = new Phaser(); Monitor amonitorphaser =new Monitor(phaser); new Thread(aMonitorPhaser).start(); for (int i = 0; i < NUM_THREADS; i++) { Task atask = new Task(phaser); new Thread(aTask).start(); Fork Join Il framework fork join è probabilmente il più interessante introdotto nel package della concorrenza con Java SE 7. Le classi core di tale framework sono la ForkJoinPool e ForkJoinTask. In particolare, gli oggetti di tipo ForkJoinTask vengono eseguiti appena sottoposti ad un oggetto di tipo ForkJoinPool. Le modalità attraverso le quali un oggetto ForkJoinTask può essere sottoposto
ad un ForkJoinPool, sono le seguenti: asincrona (attraverso il metodo execute), bloccante (metodo invoke) e con l'ottenimento di un oggetto "future" a sua volta da sottomettere (metodo submit). Una volta avviato, l oggetto ForkJoinTask tipicamente avvia un insieme di attività asincrone utilizzando il metodo fork() (e quindi join()). Di seguito viene riportato un esempio di utilizzo di tale framework per l implementazione dell algoritmo di ordinamento QuickSort. Nel caso specifico, viene utilizzata la subclass RecursiveAction della ForkJoinTask e nel metodo compute invocati iterativamente gli step di sort sui due subsets di dati individuati da partition. package it.af; import java.util.arraylist; import java.util.list; import java.util.concurrent.forkjoinpool; import java.util.concurrent.recursiveaction; import java.util.logging.logger; public class QuickSort<T extends Comparable<T>> extends RecursiveAction { private static final long serialversionuid = 1L; private static final Logger LOGGER = Logger.getLogger(PhaserSample.class.getName()); private List<T> data; private int lindex; private int uindex; private long taskid; public QuickSort(List<T> data) { this.data = data; this.lindex = 0; this.uindex = data.size() - 1; this.taskid = System.nanoTime(); outputstat("new (first) "); protected QuickSort(List<T> data,int lindex,int uindex,long taskid) { this.lindex = lindex; this.uindex = uindex; this.data = data; this.taskid = taskid; outputstat("new "); private void outputstat(string operation) { String threadid = Thread.currentThread().getId()+""; if (Thread.currentThread().getId() < 10) { threadid = "0"+threadId; LOGGER.info( " task id:"+this.taskid+ " thread:"+threadid+" op:"+operation); private int partition(int pivotindex) { T pivotvalue = data.get(pivotindex); // middle element swap(pivotindex, uindex); int storeindex = lindex; for (int i=lindex; i < uindex; i++) { if (data.get(i).compareto(pivotvalue) < 0) { swap(i, storeindex); storeindex++; swap(storeindex, uindex);
return storeindex; private void swap(int i, int j) { if (i!= j) { T ivalue = data.get(i); data.set(i, data.get(j)); data.set(j, ivalue); protected void compute() { if (lindex < uindex) { outputstat("start..."); int pivotindex = lindex + ((uindex - lindex) / 2); pivotindex = partition(pivotindex); invokeall( new QuickSort<>(data, lindex,pivotindex-1, System.nanoTime()), new QuickSort<>(data, pivotindex+1,uindex, System.nanoTime())); outputstat("... End"); public static void main(string[] args) { int MAX_SIZE = 50; List<Integer> sourcedata = new ArrayList<>(MAX_SIZE); for (int i=0; i<max_size; i++) { int randomvalue = (int) (Math.random() * 100_000); sourcedata.add(randomvalue); LOGGER.info("in="+sourceData); QuickSort<Integer> quicksort =new QuickSort<>(sourceData); // Pool di 6 threads ForkJoinPool pool = new ForkJoinPool(6); pool.invoke(quicksort); LOGGER.info("out="+sourceData);