Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 8 di 68
  • livello ninja
Indice lezioni

Il pattern Singleton

Impariamo a implementare uno schema nel quale l’istanza di una classe viene condivisa da più client utilizzando il pattern Singleton.
Impariamo a implementare uno schema nel quale l’istanza di una classe viene condivisa da più client utilizzando il pattern Singleton.
Link copiato negli appunti

Fino ad ora abbiamo descritto richieste client gestite in modo isolato l'una dall'altra senza
problemi di concorrenza, a volte però può essere utile ed efficiente implementare uno schema
nel quale l'istanza di una classe è condivisa da più client.

A partire da Ejb 3.1 abbiamo un nuovo
componente session che implementa questo schema: singleton session bean; l'Ejb Object (proxy) effettuerà il routing delle richieste
dei client verso l'unica istanza di session bean. Lo scenario nel quale opera un bean singleton è quindi il seguente:

  1. stato dell'istanza condiviso da più client;
  2. più richieste contemporanee verso il bean;
  3. Ejb singleton thread-safe a causa delle invocazioni concorrenti;
  4. schema di locking e sincronizzazione realizzato in modo efficiente.

Un singleton session bean può essere una scelta estremamente efficiente se applicata correttamente, dobbiamo
però prestare attenzione al suo utilizzo, adottato in circostanze sbagliate o con strategie di locking errate
può portare a gravi problemi di prestazioni.

Il locking

Il locking è il meccanismo con cui un thread acquisisce temporaneamente l'uso esclusivo
di un oggetto o del metodo di un oggetto. Questo si può raggiungere in Java attraverso l'uso di metodi o blocchi synchronized.

In Ejb 3.1 abbiamo un'astrazione semplificata del meccanismo di locking nella forma del Container-Managed-Concurrency (CMC).
Di default, un singleton session bean, implementa una gestione della concorrenza
gestita dal Container (quella che vedremo in questa guida), altri tipi possibili sono BEAN e CONCURRENCY_NOT_SUPPORTED. Nel caso di CMC ad ogni metodo del session bean singleton possiamo assegnare uno dei seguenti tipi di lock: Read e Write (default).

Un metodo con un lock di tipo Write può essere eseguito da un thread alla volta. Quando un client
invoca un metodo con lock Write acquisisce un lock in scrittura sul bean singleton, questo significa che ogni altro thread
deve attendere il completamento del thread che ha l'uso esclusivo del singleton per poter eseguire metodi.

Il lock di tipo Read deve essere specificato e consente accesso completo ad un metodo da parte di più thread contemporaneamente, a patto che
non sia già bloccato in scrittura da un altro thread.

Può essere utile applicare un singleton quando dobbiamo leggere dati che vengono modificati poco frequentemente, in queste situazioni
raramente un thread in lettura sarà bloccato da un thread in scrittura. Supponiamo di avere un portale Web al quale aggiungere ogni mattino le news del giorno, avremo quindi in media una singola
operazione di scrittura giornaliera ma molte richieste in lettura. Decidiamo quindi di realizzare un componente singleton che legga
le news pronto a servire le richieste. Seguendo lo schema di progettazione dei session bean introdotto
nei capitoli precedenti, definiamo l'interfaccia per il contratto con il client. Creiamo il package it.html.progetto1.singleton.ejb32 e
al suo interno le interfacce NewsSingleton, NewsSingletonLocal, NewsSingletonRemote:

package it.html.progetto1.singleton.ejb32;
import java.util.List;
public interface NewsSingleton<T> {
	  List<T> getNews();
	  void addNews(T news);
	  void removeNews(T news);
}

package it.html.progetto1.singleton.ejb32;
import javax.ejb.Local;
@Local
public interface NewsSingletonLocal<T> extends NewsSingleton<T> {}

package it.html.progetto1.singleton.ejb32;
import javax.ejb.Remote;
@Remote
public interface NewsSingletonRemote<T> extends NewsSingleton<T> {}

Implementiamo la classe bean che realizza il contratto:

package it.html.progetto1.singleton.ejb32;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.ejb.AccessTimeout;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.ejb.Startup;
@Singleton
@Startup
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class NewsSingletonBean implements NewsSingletonLocal<String>, NewsSingletonRemote<String> {
   private List<String> newsList;
   public NewsSingletonBean(){
    newsList=new ArrayList<String>();
   }
   @Lock(LockType.READ)
   @AccessTimeout(unit=TimeUnit.SECONDS, value=15)
   public List<String> getNews(){
    return newsList;
   }
   @Lock(LockType.WRITE)
   public void addNews(String news){
    newsList.add(news);
   }
   @Lock(LockType.WRITE)
   public void removeNews(String news){
    newsList.remove(news);
   }
}

L'annotation Singleton è abbastanza intuitiva, marca la classe come session bean singleton. L'annotation
@Startup fa in modo che il bean venga allocato in fase di deploy del modulo Ejb. Infine abbiamo l'annotation @Lock
che realizza i meccanismi di lock esposti in precedenza.

Esiste però anche l'annotation @AccessTimeout: invece
di avere un thread che, dopo aver invocato un metodo con lock Read, si trova in attesa perché il singleton è in lock da parte di un altro
thread, possiamo specificare un limite per l'attesa (in questo caso 15 secondi).
Allo scadere del timeout, se il lock non è stato rilasciato si verifica un'eccezione.

Nell'esempio proposto abbiamo fatto
uso dell'annotation @ConcurrencyManagement solo per scopi illustrativi, il comportamento ConcurrencyManagementType.CONTAINER è
impostato di default e quindi non è necessario specificarlo se intendiamo utilizzare la Container-Managed-Concurrency. Come
per gli altri session bean realizziamo uno unit test modificando la classe IntegrationTestCase:

package it.html.progetto1.test;
import java.util.Properties;
import javax.naming.*;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import it.html.progetto1.ejb30.ConverterBeanRemote;
import it.html.progetto1.singleton.ejb32.NewsSingleton;
import it.html.progetto1.singleton.ejb32.NewsSingletonRemote;
import it.html.progetto1.stateful.ejb32.ShoppingCartRemote;
import junit.framework.TestCase;
public class IntegrationTestCase {
    private static Context namingContext;
    private static ConverterBeanRemote converteBeanRemote;
    private static ShoppingCartRemote<String> shoppingCartRemote;
    private static NewsSingleton<String> newsSingletonRemote;
    private static final String CONVERTER_REMOTE_JNDI_NAME="/Progetto1Ear/ProgettoEjb1/ConverterBean!it.html.progetto1.ejb30.ConverterBeanRemote";
    private static final String SHOPPING_CART_REMOTE_JNDI_NAME="/Progetto1Ear/ProgettoEjb1/ShoppingCartBean!it.html.progetto1.stateful.ejb32.ShoppingCartRemote";
    private static final String SINGLETON_REMOTE_JNDI_NAME="/Progetto1Ear/ProgettoEjb1/NewsSingletonBean!it.html.progetto1.singleton.ejb32.NewsSingletonRemote";
    @BeforeClass
    public static void obtainProxyReferences() throws NamingException{
        Properties jndiProperties = new Properties();
        jndiProperties.put("jboss.naming.client.ejb.context", true);
        jndiProperties.put(Context.PROVIDER_URL, "http-remoting://127.0.0.1:8080");
        jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
        namingContext = new InitialContext(jndiProperties);
    }
    @Test
    public void testConverter() throws NamingException{
        converteBeanRemote = (ConverterBeanRemote)namingContext.lookup(CONVERTER_REMOTE_JNDI_NAME);
        TestCase.assertEquals(converteBeanRemote.celsiusToFahrenheit(30.0f), 86.0f);
        TestCase.assertEquals(converteBeanRemote.fahrenheitToCelsius(86.0f), 30.0f);
    }
    @SuppressWarnings("unchecked")
    @Test
    public void testShoppingCart() throws NamingException{
        shoppingCartRemote = (ShoppingCartRemote<String>)namingContext.lookup(SHOPPING_CART_REMOTE_JNDI_NAME);
        System.out.println("Aggiunta elemento 1");
        shoppingCartRemote.addItem("Item1");
        System.out.println("Aggiunta elemento 2");
        shoppingCartRemote.addItem("Item2");
        System.out.println("Lista elementi:");
        for(String item : shoppingCartRemote.getItems()){
            System.out.println(item);
        }
        System.out.println("Rimozione elemento 1");
        shoppingCartRemote.removeItem("Item1");
        System.out.println("Lista elementi:");
        for(String item : shoppingCartRemote.getItems()){
            System.out.println(item);
        }
        shoppingCartRemote.releaseShoppingCart();
    }
    @SuppressWarnings("unchecked")
    @Test
    public void testSingleton() throws NamingException{
        newsSingletonRemote = (NewsSingletonRemote<String>)namingContext.lookup(SINGLETON_REMOTE_JNDI_NAME);
        System.out.println("Aggiunta news 1");
        newsSingletonRemote.addNews("News1");
        System.out.println("Aggiunta news 2");
        newsSingletonRemote.addNews("News2");
        System.out.println("News :");
        for(String news : newsSingletonRemote.getNews()){
            System.out.println("News:"+news);
        }
        System.out.println("Rimozione news 1");
        newsSingletonRemote.removeNews("News1");
        System.out.println("News :");
        for(String news : newsSingletonRemote.getNews()){
            System.out.println("News:"+news);
        }
    }
    @AfterClass
    public static void tearDownClass() throws NamingException {
        namingContext.close();
    }
}

Ti consigliamo anche