a cura di Dario Dariol
 
    Soluzione Esercizio 006 - Test and Set

    Quale era il difetto riscontrato nel settembre del 1999 da un mio amico nella seguente proposta di realizzazione di Test-and-Set in Java?

       
      final class TestAndSet {
       private boolean busy;
       TestAndSet(boolean initialBusy){
         busy = initialBusy;
       }
       TestAndSet(){
         this(true);
       }
       synchronized void releaseResource(){
         busy = false;
         try {notify();} catch (Throwable t) {}
       }
       synchronized void lockResourceWaiting(){
         while (busy)
           try {wait();} catch (Throwable t) {}
         busy = true;
       }
       synchronized boolean testAndLockResource(){
         if (busy)       // already locked
           return false; // by another
         else {
           busy = true;
           return true;  // locked by me
         }
       }
      }


    Ebbene la precedente realizzazione java fa esattamente quello che deve fare: realizza cioè con successo una primitiva di Test-and-Set! Il vero problema è che questa implementazione è soggetta ad un attacco DOS (Denial of Service).

    Se non sapete cos'è un attacco DOS, oppure volete semplicemente rinfrescarvi la memoria, potete consultare due ottime pagine in rete: quella del CERT del Software Engineering Institute della Carnegie Mellon, oppure la Denial of Service (DoS) Attack Resources che contiene un buon insieme di link (e anche una simpatica vignetta introduttiva...).

    Ma torniamo a noi: qual'è il problema nel codice qui sopra? Il problema è dovuto al fatto che un qualsiasi altro thread della mia applicazione Java può bloccare l'intera applicazione (o per lo meno quella parte applicativa che usa TestAndSet) con un semplice metodo tipo il seguente:
     

      void BlockTestAndSet(TestAndSet ts){
        synchronized(ts) {
          while (true)
            ;
      }


    Infatti durante l'esecuzione (infinita!) di BlockTestAndSet chiunque altro provi ad usare un qualunque metodo della TestAndSet su quella istanza rimane bloccato per sempre. Il problema è dovuto al fatto che TestAndSet offre dei metodi pubblici synchronized sullo stesso oggetto TestAndSet. Per ovviare a questo problema è sufficiente riscrivere TestAndSet nel seguente modo:
     

      final class TestAndSet{
        private boolean busy;
        private Object sem;

        TestAndSet(boolean initialBusy){
          busy = initialBusy;
          sem = new Object();
        }

        TestAndSet(){
          this(true);
        }

        void releaseResource(){
          synchronized (sem) {
            busy = false;
            try { notify();} catch (Throwable t) {}
          }
        }

        void lockResourceWaiting(){
          synchronized (sem) {
            while (busy)
              try { wait();} catch (Throwable t) {}
            busy = true;
          }
        }

        boolean testAndLockResource(){
          synchronized (sem) {
            if (busy)
              return false; // already locked by another
            else {
              busy = true;
              return true;  // locked by me
            }
          }
        }
      }


    In questa seconda implementazione il semaforo su cui si fa synchronized è un oggetto privato di TestAndSet e quindi non è accessibile all'esterno e quindi non è lockabile da un altro thread.

    Abbiamo quindi imparato come nascondere i dettagli implementativi all'interno della nostra implementazione e lasciare visibile all'esterno solo i metodi non synchronized effettivamente necessari.

    Non abbiamo comunque completamente risolto il problema: un hacker-thread può sempre, dopo aver ottenuto il controllo della risorsa, non rilasciarla mai, non chiamando mai la releaseResource. In questo modo gli altri threads che usino la lockResourceWaiting rimarranno per sempre bloccati. Ma anche in questo caso c'è una scappatoia: se invece gli altri threads usano la testAndLockResource possono decidere cosa fare se trovano la risorsa occupata per troppo tempo!

    Ricordiamo comunque sempre che, come recita il paragrafo finale della sezione 17.13 della specifica di Java:

      The Java programming language does not prevent, nor require detection of, deadlock conditions. Programs where threads hold (directly or indirectly) locks on multiple objects should use conventional techniques for deadlock avoidance, creating higher-level locking primitives that don't deadlock, if necessary.
 




MokaByte®  è un marchio registrato da MokaByte s.r.l.

Java® è un marchio registrato da Sun Microsystems; tutti i diritti riservati

E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a
mokainfo@mokabyte.it