martedì 31 gennaio 2012

Introduzione a java e generics parte 4 - Dichiarazioni di variabili


Costruttori

public class Coppia<P,S> {
 private final P primo; 
 private final S secondo; 
 public P getPrimo() {
  return primo; } 
 public S getSecondo() {
  return secondo; } 
 public Coppia(P primo, S secondo) {
  this.primo = primo;
  this.secondo = secondo; }}
In una classe generica i parametri di tipo appaiono nell'intestazione che dichiara la classe non nel costruttore!
Coppia<String, Integer> c = new Coppia<String, Integer>("Ciao", 1);
assert c.getPrimo().equals("Ciao") && c.getSecondo().equals(1);
OK
Coppia<String,Integer> c = new Coppia("Ciao",1);
WarningUncheckedWarning L'istruzione è legale ma genera un warning (non è detto che valga la Cast Iron Guarantee)

Static

List<Integer> interi = Arrays.asList(1,2,3);
List<String> stringhe = Arrays.asList("ciao","mondo");
assert interi.getClass() == stringhe.getClass();
I generics sono implementati tramite type erasure List<String> e List<Integer> a tempo di compilazione sono List
Type erasure →
  • variabili e metodi statici di classi generiche sono condivise tra tutte le istanze della classe
  • le variabili e metodi statici di una classe generica non possono far riferimento ai parametri di tipo della classe accedendo ad un metodo statico di una classe generica il nome della classe non deve essere parametrizzato (con la variabile di tipo)

Esempio static

public class Cella<T> { 
 private final int id; 
 private final T valore; 
 public static int count = 0;
 public static synchronized int getCount() { return count;} 
 public static synchronized int nextId() { return count++;} 
 public int getId() { return id; }
 public T getValore() { return valore;} 
 public Cella( T valore) { this.id = nextId(); this.valore = valore;}
...
Vediamo la classe all'opera
Cella<String>cs = new Cella<String>("ciao");
Cella<Integer> ci = new Cella<Integer>(1);
assert cs.getId() == 0 && ci.getId() == 1
       && Cella.getCount() == 2;
Il contatore è condiviso tra tutte le istanze della classe Cella

Utilizzo di metodi static

  • Cella.getCount() OK
  • Cella<Integer>.getCount() NO
  • Cella<?>.getCount() NO

Inner class

Java permette di annidare una classe dentro un'altra Se la classe esterna ha un parametro di tipo T:
  • Classe interna non statica → può accedere direttamente al parametro di tipo T
  • Classe interna statica→ non può accedere (direttamente) al parametro di tipo T ma esiste un escamotage …

Inner class non statica

public class Esterna<G> { ...
 private class Interna1{ 
  Gg =null; ...
 } ...
}

Inner class statica: escamotage

public class Esterna<G> { ...
public G getPippo() { return new Interna2<G>().getPippo();
} public static class Interna2<H>{
public H getPippo() { ...
} ...

Check than act

A cura di Flavio Casadei Della Chiesa
Leggi l'articolo completo su flaviocdc.net 
Chect then Act, schema di codice nel quale si effettua un controllo prima di eseguire un'azione.
// Check then act
.....
    if (test) {
        azione();
    } else  {
        altraAzione();
    }
.....
utilizzare questo codice in un ambiente concorrente può avere risultati inaspettati. E' infatti possibile che dopo aver eseguito il test la condizione sia cambiata, in questo caso potrebbe venir invocata l'azione sbagliata.

Java Memory Model

Leggi l'articolo completo su flaviocdc.net
 
Java Memory Model è il capitolo 17 di Java Language Specification.
Un memory model definisce i possibili scenari e regole che governano più Thread all'interno di uno stesso sistena. In memory model determina se alla JVM è permessa una particolare esecuzione di un programma. Le specifiche Java non forzano le implementazioni delle JVM a seguire qualche particolare regola per l'esecuzione di un programma; questo da agli implementatori della JVM una grande flessibilità spesso utilizzata per permettere al compilatore ottimizzazioni aggressive basate spesso sul riordino delle istruzioni. Comunque sia il memory model di Java spcifica che tutte le implementazioni devono produrre risultati predicibili da parte del programmatore (se vengono da lui/lei seguite certe regole).
Il memory model definisce regole per l'utilizzo dei Thread ed il comportamento atteso di programmi multithread così da permettere agli sviluppatori di impostare di conseguenza i propri programmi. La responsabilità di evitare RaceCondition, deadlock ed altro sta tutta nel lavoro dello sviluppatore.

Triade della morte

Leggi l'articolo completo su flaviocdc.net

Per “Triade della morte” in ambiente multuthread mi riferisco alle tre maggiori cause di problemi derivanti da una scorretta gestione della concorrenza.
La triade è composta da
  • Riordino delle istruzioni da partedi JVM, CPU, Memoria, ….
  • Problemi di visibilità delle modifiche effettuate da un Thread (Quando il Thread B può vedere il valore corretto della variabile X scritta dal Thread A?)
Il più famoso antipattern nel quale è presente anche se molto nascosta questa triade è il Double checked locking idiom

Put if absent, variante del CheckThenAct

A cura di Flavio Casadei Della Chiesa
Leggi l'articolo completo su flaviocdc.net
  
In questo schema si provvede ed inserire un elemento (di solito) in una collezione solo se questa già non lo contiene.
 
// schema generico di put-if-absent
private HashMap<K,V> hm .....
    public void putIfAbsent(K k , V v) {
        if (!hm.containsKey(k)) {
                hm.put(k,v);
        }
    }
i problemi sono i soliti derivanti dal CheckThenAct. Nota, come in tutte le azioni composte non basta limitarsi ad aggiungere un synchronized per risolvere il problema.
Per eliminare ogni sorta di problema è necessario rendere atomica l'operazione. Per far questo solo un Thread deve essere in grado di controllare la presenza o meno della chiave nella mappa e di effettuare un'eventuale sostituzione.
Un esempio di soluzione può essere il seguente nel quale si istanzia una HashMap all'interno di una classe contenitrice
public class InnerPIA<K,V>  {
 private final HashMap<K,V>  delegate = new HashMap<K,V>();
 public synchronized V get(K k)  {return delegate.get(k);}
 public synchronized void put(K k, V v)  { delegate.put(k,v);}
 public synchronized  boolean containsKey(K k) {return delegate.containsKey(k);}
public synchronized void putIfAbsent(K k, V v)  {
 if (!delegate.containsKey(k) {
     delegate.put(k,v);
 }
}
.....
 }
Nel caso in cui la HashMap sia condivisa e non confinata in una classe contenitrice è necessario utilizzare un altro tipo di ClientSideLocking. In questo modo però tutte le classi che condividono la solita istanza della mappa devono utilizzare il solito protocollo.
public class DelegatePIAPIA<K,V>  {
 private final Map<K,V>  delegate ;
 public DelegatePIA(Map<K,V> m) {
  this.delegate = m;
 }
 public  V get(K k)  {
  synchronized(delegate) {
   return delegate.get(k);
  }
 }
 public  void put(K k, V v)  {
  synchronized(delegate) {
   delegate.put(k,v);
  }
 }
 public   boolean containsKey(K k) {
  synchronized(delegate) {
   return delegate.containsKey(k);
  }
 }
public  void putIfAbsent(K k, V v)  {
  synchronized(delegate) {
   if (!delegate.containsKey(k) {
    delegate.put(k,v);
   }
  }
}
.....
 }
In Java5, precisamente nella concurrent API, esiste un modo più immediato: utilizzare una implemantazione di una ConcurrentMap come ad esempio la classe ConcurrentHashMap.
Queste dispongono di un metodo chiamato appunto putIfAbsent la cui firma è simile alla precedente; l'unica differenza è che il metodo non è void ma ritorna n elemento di tipo V. Invocare il metodo
pippo = mappa.putIfAbsent(key,value);
è equivalente a
 if (!mappa.containsKey(key)) 
      return mappa.put(key, value);
   else
      return mappa.get(key);
Se la mappa conteneva un valore precedente per la chiave key il metodo ritorna tale valore, altrimenti restituisce null.

Read Write Lock

Leggi l'articolo completo su flaviocdc.net
BOZZA! Sono un meccanismo per una gestione ottimale dell'accesso a risorse condivise basato sul principio che se molte azioni possono avvenire senza problemi in modo concorrente e solo poche in modo esclusivo allora non c'è alcuna necessità di forzare un accersso in mutua esclusione alla risorsa condivisa.

Double checked locking idiom

Leggi l'articolo completo su flaviocdc.net 

A cura di Flavio Casadei Della Chiesa

Copyright (c)  2006  Flavio Casadei Della Chiesa.
     è garantito il permesso di copiare, distribuire e/o modificare
     questo documento seguendo i termini della Licenza per
     Documentazione Libera GNU, Versione 1.1 o ogni versione
     successiva pubblicata dalla Free Software Foundation.

Il Double Checked Locking Idiom è uno dei più subdoli Antipattern della programmazione concorrente in Java. A prima vista sembra un utile strumento per migliorare le performance, tuttavia sotto le regole dell'attuale Java Memory Model questo schema non funziona :-?
public class DoubleCheckedLocking {
    private static Resource _instance
    public static Resource getInstance() {
        if (_instance == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (_instance == null)
                    _instance = new Resource();
                }
            }
       }
       return _instance;
    }
}
Inizializzare un oggetto comporta la scrittura di alcune variabili (stato dell'oggetto), pubblicare un oggetto riguarda la scrittura di altre variabili (il reference). Se non si assicura che pubblicare l'oggetto accada prima che un Thread possa leggerne il reference la scrittura del reference può essere riordinata con le scritture dello stato dell'oggetto.
In tale caso un Thread può vedere un valore aggiornato per il reference, ma un valore non aggiornato per alcune delle variabili che compongono lo stato dell'oggetto. Si ottiene quindi il reference ad un oggetto parzialmente costruito.
Senza utilizzare tecniche troppo elaborate e spesso inutili è possibile risolvere il problema con il seguente codice
public class EagerInstantiation {
 private static final Resource _instance = new Resource();
 public static Resource getResource() {
  return _instance; // :-)
 }
}

Alcuni riferimenti

Nested Monitor Problem

Leggi l'articolo completo su flaviocdc.net 

' Versione da riguardare '
' A cura di Flavio Casadei Della Chiesa '
Il Nested Monitor Problem (NMP) si ha in genere quando un Thread entrando nella coda di wait di un oggetto “mette a dormire con se” il lock dell'unico oggetto che può “risvegliare” tale Thread tramite una notify*().
In generale è buona norma 'non' mettere un Thread in una coda di wait se non si è sicuri che quancun altro Thread sia in grado di eseguire una notify* su di essa (e quindi liberare i Thread in attesa). A volte nonostante sia stato predisposto un adeguato sistema di metodi di notifica e attesa può accadere che il metodo di notifica non possa essere eseguito in quanto il suo accesso è “sbarrato” da un lock che non potrà mai essere acquisito. Vediamo un esempio di NMP
class Inner {
  protected boolean cond = false;
  public synchronized void await ()   {
    while (!cond)
      try { wait (); }
      catch (InterruptedException e) {}
      // .....
  }
  public synchronized void signal (boolean c)   {
    cond = c;
    notifyAll ();
  }
}
class Outer {
  protected Inner inner_ = new Inner (); // nota! non è accessibile all'esterno
  public synchronized void process ()   {
    inner_.await ();
  }
  public synchronized void set (boolean c)   {
    inner_.signal (c);
  }
}
Nella classe denominata Inner i metodi di attesa e notifica sono giustamente messi pubblici e synchronized; la classe Outer si limita solamente ad utilizzare tali metodi in blocchi sincronizzati al fine di evitare accessi concorrenti. A prima vista sembrerebbe tutto a posto tuttavia in questo frammento di codice si cela un'insidia poco visibile.
Prima di entrare nella coda di wait di un oggetto X, un generico Thread T ne deve acquisire il lock intrinseco (senza se e senza ma), una volta entrato in wait tale lock viene rilasciato e messo a disposizione di altri Thread. Fin qua niente di nuovo, tuttavia se leggiamo attentamente non ho scritto che 'tutti' i lock acquisiti dal Thread T vengono rilasciati; ed infatti solo il lock intrinseco viene rilasciato, gli altri no. Se osserviamo il NMP da vicino si nota che per un Thread T1 che invoca Outer.process() acquisisce ben due lock: quello di Outer e quello di Inner, ma una volta entrato in wait (su inner) il lock di Outer rimane acquisito e non rilasciato. Ogni altro Thread T2 che prova ad invocare Outer.set() rimane bloccato all'infinito in quanto il lock di Outer è in possesso di T1. Non esiste quindi alcun modo per riacquisire il lock di Outer in quanto nessun Thread è in grado di eseguire la notify().
La Nested in Nested Monitor Problem deriva dal fatto che la classe che esegue la wait di norma è una classe interna/annidata della classe che espone i metodi ai client; questa di norma è una buona tecnica di confinamento che limita alcuni problemi di concorrenza anche se, come abbiamo visto, può generare probkemi ben più gravi.
public class Outer {
        protected Nested _nested = new Nested (); // nota! non è accessibile all'esterno
        public synchronized void process ()   {
                _nested.await ();
        }
        public synchronized void set (boolean c)   {
                _nested.signal (c);
        }
        private class Nested {
                protected boolean cond = false;
                public synchronized void await ()   {
                        while (!cond)
                                try { wait (); }
                        catch (InterruptedException e) {}
                        // .....
                }
                public synchronized void signal (boolean c)   {
                        cond = c;
                        notifyAll ();
                }
        }
}

Nested Monitor Solution?
Beh le soluzioni sono molte e non tutte applcabili in ogni contesto. Analizziamo una ipotetica soluzione ne propongo una basata su classi annidate (nested)
public class NestedMonitor {
        private final Nested _nested = new Nested (); // nota! non è accessibile all'esterno
        public  void process ()   {
                _nested.await (); // nessun lock acquisito
        }
        public  void set (boolean c)   {
                _nested.signal (c); // nessun lock acquisito
        }
        private class Nested {
                protected boolean cond = false;
                public synchronized void await ()   {
                        while (!cond)
                                try { wait (); }
                        catch (InterruptedException e) {Thread.currentThread().interrupt(); return ;}
                        // .....
                }
                public synchronized void signal (boolean c)   {
                        cond = c;
                        notifyAll ();
                }
        }
}
Come si nota, la soluzione consiste nel rimuovere la keyword synchronized dai metodi della classe Outer, più qualche “aggiunta” ….. Non si notano a prima vista grossi problemi, ma riflettendo meglio non è esclusa la possibilità di generare il solito problema nel caso in cui una classe cliente tenti un client side locking su una istanza della classe Nestedmonitor.
Attenzione quindi a chiamare metodi “alieni” con un lock acquisito!
Propongo due versioni alternative di una ipotetica soluzione, presto seguiranno commenti … spero pure i vostri! Contattatemi pure all'indirizzo fcasadei CHIOCCIOLA gmail PUNTO com
public class Outer {
        private final Nested _nested = new Nested (); // nota! non è accessibile all'esterno
        private final Object lock = new Object();
        public  void process ()   {
                _nested.await (); // nessun lock acquisito
        }
        public  void set (boolean c)   {
                _nested.signal (c); // nessun lock acquisito
        }
        private class Nested {
                protected boolean cond = false;
                public  void await ()   {
                        synchronized (lock) { //prendo il lock "giusto"
                                while (!cond)
                                        try { lock.wait (); }
                                catch (InterruptedException e) {Thread.currentThread().interrupt(); return ;}
                                // .....
                        }
                }
 
                public  void signal (boolean c)   {
                        synchronized (lock) {
                                cond = c;
                                lock.notifyAll ();
                        }
                }
        }
}
public class NestedOuter {
        private final Nested _nested = new Nested (); // nota! non è accessibile all'esterno
 
        public  void process ()   {
                _nested.await (); // nessun lock acquisito
        }
        public  void set (boolean c)   {
                _nested.signal (c); // nessun lock acquisito
        }
        private class Nested {
                protected boolean cond = false;
                public  void await ()   {
                        synchronized (NestedOuter.this) { //prendo il lock "giusto"
                                while (!cond)
                                        try { NestedOuter.this.wait (); }
                                catch (InterruptedException e) {Thread.currentThread().interrupt(); return ;}
                                // .....
                        }
                }
 
                public  void signal (boolean c)   {
                        synchronized (NestedOuter.this) {
                                cond = c;
                                NestedOuter.this.notifyAll ();
                        }
                }
        }
}

Unsafe lazy instantiation

Leggi l'articolo completo su flaviocdc.net 

In questo Antipattern sono presenti vari problemi, il più ovvio è quello alla creazione di finti Singleton. L'accesso non esclusivo alla getInstance() può favorire la creazione di più istanze differenti della risorsa. Altri e più subdoli problemi sono discussi nel Double checked locking idiom
public class UnsafeLazyInstantiation {
    private static Resource _instance;
    public static Resource getInstance() {
        if (_instance == null)
            _instance = new Resource(); // unsafe publication :-P
        return _instance;
    }
.......
}
La soluzione immediata al problema dei finiti Singleton è la seguente
public class SimpleLazyInstantiation {
    private static Resource _instance;
    public synchronized static Resource getInstance() {
        if (_instance == null)
            _instance = new Resource(); //  sate publication :-|
        return _instance;
    }
.......
}
si nota in fatti che il metodo getInstance() è sincronizzato, non è quindi possibile che due o più Thread vi accedano in contemporanea.
Nonostante il precedente codice sia sicuro può non essere sempre un'ottima cosa sincronizzare il metodo di istanziazione. Java permette il caricamento lazy di una classe tramite il LazyClassLoading utilizzabile tramite gli inizializzatori statici. La versione pulita per l'instanziazione lazy è la seguente
public class EagerInstantiation {
    private static final Resource _instance = new Resource();
    public static Resource getResource() {
        return _instance; // :-)
    }
}