2016-03-22 13 views
14

Desidero utilizzare le classi SOAPConnectionFactory e MessageFactory da SAAJ con più thread, ma risulta che non posso presumere che siano thread-safe. Alcuni Related posts:Creazione di un oggetto non thread-thread per thread e utilizzo della garanzia before-before

Ecco un po 'la prova interessante che può essere thread-safe: http://svn.apache.org/repos/asf/axis/axis2/java/core/tags/v1.5.6/modules/saaj/src/org/apache/axis2/saaj/SOAPConnectionImpl.java si dice

Anche se la sicurezza dei thread non è esplicitamente richiesto dalle specifiche SAAJ, sembra che il SOAPConnection nell'implementazione di riferimento di Sun è thread-safe.

Ma ancora non penso che sia una prova sufficiente per trattare le classi SAAJ come thread-safe.

Quindi la mia domanda: l'idioma è corretto? Creo esattamente un oggetto SOAPConnection e MessageFactory utilizzando le possibili fabbriche non thread-thread all'interno del thread principale e quindi pubblichiamo in modo sicuro tali oggetti su un'attività executor utilizzando la garanzia first-before dell'interfaccia di CompletionService. Uso anche questo, prima di garantire di estrarre l'oggetto HashMap risultato.

Fondamentalmente voglio solo verificare la sanità mentale del mio ragionamento.

public static void main(String args[]) throws Exception { 
    ExecutorService executorService = Executors.newFixedThreadPool(10); 
    CompletionService<Map<String, String>> completionService = new ExecutorCompletionService<>(executorService); 

    //submitting 100 tasks 
    for (int i = 0; i < 100; i++) { 
     // there is no docs on if these classes are thread-safe or not, so creating them before submitting to the 
     // external thread. This seems to be safe, because we are relying on the happens-before guarantees of the 
     // CompletionService. 
     SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance(); 
     SOAPConnection soapConnection = soapConnectionFactory.createConnection(); 
     MessageFactory messageFactory = MessageFactory.newInstance(); 
     int number = i;// we can't just use i, because it's not effectively final within the task below 
     completionService.submit(() -> { 
      // using messageFactory here! 
      SOAPMessage request = createSOAPRequest(messageFactory, number); 
      // using soapConnection here! 
      SOAPMessage soapResponse = soapConnection.call(request, "example.com"); 
      soapConnection.close(); 
      ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 
      soapResponse.writeTo(outputStream); 
      // HashMap is not thread-safe on its own, but we'll use the happens-before guarantee. See f.get() below. 
      Map<String, String> result = new HashMap<>(); 
      result.put("soapResponse", new String(outputStream.toByteArray())); 
      return result; 

     }); 
    } 

    // printing the responses as they arrive 
    for (int i = 0; i < 100; i++) { 
     Future<Map<String, String>> f = completionService.take(); 
     Map<String, String> result = f.get(); 
     System.out.println(result.get("soapResponse")); 
    } 

    executorService.shutdown(); 
} 

/** 
* Thread-safe static method 
*/ 
private static SOAPMessage createSOAPRequest(MessageFactory messageFactory, int number) throws Exception { 
    SOAPMessage soapMessage = messageFactory.createMessage(); 
    SOAPPart soapPart = soapMessage.getSOAPPart(); 

    String serverURI = "example.com"; 

    SOAPEnvelope envelope = soapPart.getEnvelope(); 
    envelope.addNamespaceDeclaration("example", serverURI); 

    SOAPBody soapBody = envelope.getBody(); 
    SOAPElement soapBodyElem = soapBody.addChildElement("number", "example"); 
    soapBodyElem.addTextNode(String.valueOf(number)); 

    soapMessage.saveChanges(); 

    return soapMessage; 
} 
+1

Ora non si sta creando istanza per thread, ma istanza per attività (quindi verranno create 100 istanze di ciascuna). Perché non usi 'TreadLocal' per ridurre l'istanziazione e riutilizzarli in compiti non interferenti? –

+1

Mi chiedo solo: perché utilizzi un main static per il test; e non i test unitari? – GhostCat

+0

@SashaSalauyou Sì, istanza per compito in realtà. Ma non penso che cambi molto. In teoria, potrei creare un oggetto all'interno di "submit" e un po 'nasconderlo dentro ThreadLocal per il caso in cui un'altra attività viene eseguita all'interno dello stesso thread, ma dovrò comunque richiamare almeno SOAPConnectionFactory.newInstance() all'interno del codice dell'attività blocco che si assume qui per non essere thread-safe. Fammi sapere se mi manca qualcosa. Inoltre, non mi interessa "riutilizzarli" così tanto. La chiamata di servizio è di circa 1 minuto per la mia situazione, quindi la velocità di creazione degli oggetti non è un collo di bottiglia. – Ruslan

risposta

7

Sì, il tuo ragionamento su CompletionService è corretto..

In genere, tuttavia, non è necessario. statico i metodi di fabbrica dovrebbero sempre essere thread-safe, perché c'è no modo per assicurarsi che non siano in uso in qualche altro thread senza conoscenza globale dell'intera JVM, e non si può veramente scrivere codice che si basa su quello in molti ambienti. A volte vedrai un'implementazione che potrebbe avere un problema se un thread tenta di usarlo mentre un altro lo sta configurando, tuttavia, anche questo è raro.

Immaginate un servlet che utilizza SOAPConnectionFactory. È impossibile sapere che non ci sono altre applicazioni Web nella stessa JVM che non la stanno utilizzando contemporaneamente, quindi deve essere thread-safe.

Quindi, davvero, MessageFactory.newInstance() e SOAPConnectionFactory.newInstance() sarebbero semplicemente sbagliati se non fossero thread-safe. Li userei in più thread senza preoccupazioni, e controllo la fonte se sei davvero preoccupato. Ma davvero stanno bene.

D'altra parte, gli oggetti (anche altre fabbriche) creati da metodi di produzione statici spesso non sono thread-safe e non si deve presumere che siano privi di documentazione che lo dice. Anche il controllo dell'origine non è sufficiente, perché se le interfacce non sono documentate come thread-safe, allora qualcuno può aggiungere lo stato non sicuro a un'implementazione successiva.

+0

Ottima risposta! Mi piace anche la discussione sulle fabbriche statiche. Tuttavia, potresti fornire alcuni link di prova per dimostrare che "Un factory statico non thread-safe è un bug"? Penso di capire principalmente la tua spiegazione, ma voglio solo assicurarmi che non mi manchi niente ed è davvero un modo di pensare generalmente accettato. – Ruslan

+1

Link di prova? Ah, no, mi dispiace. Ho fatto questo genere di cose per molto tempo, quindi non convalidare il mio ragionamento contro persone casuali su internet. Sentiti libero di rimuovere il tuo segno di spunta se ti infastidisce. Se vuoi essere veramente sicuro, controlla il codice sorgente. –

+0

Sembra che mi manchi un concetto o un idioma comune. Ecco perché ti ho chiesto di dare qualche prova. Solitamente i ragazzi di Java si riferiscono a "Effective Java" o "Java Concurrency in Practice".Ho cercato di trovare almeno "una gente casuale in internet" spiegando che "Una fabbrica statica non infallibile è un bug", ma senza successo. Quindi, volevo solo essere sicuro che fosse vero. E controllare il codice sorgente è quello che ho cercato di evitare in primo luogo come il metodo meno sicuro qui. – Ruslan

2

Ho provato il codice, sembra che si sta creando un soapConnection attraverso un soapConnectionFactory che è perfettamente bene. Il seguente metodo in SAAJ 1.3 restituisce una nuova istanza di MessageFactory

public static MessageFactory newInstance(String protocol) throws SOAPException { 
    return SAAJMetaFactory.getInstance().newMessageFactory(protocol); 
} 

ci sono info in descrizione sul filo di sicurezza ma guardando il codice, sembra che questo metodo utilizza principalmente pila variabili, ad esempio ha un oggetto SOAPConnection nello stack e lo usa. Non riesco a vedere un problema se soapConnection.call (request, "esempio.com") viene chiamato da più thread nonostante non ci siano blocchi sincronizzati.

Ci si aspetterebbe che le discussioni avrebbero mandato il loro messaggio risultato attraverso diverse connessioni

+0

Non risponde alla mia domanda. Non stavo chiedendo se quelle fabbriche fossero sicure o meno. Ho già messo la mia ricerca alla risposta e ho concluso che non ci sono prove sufficienti per trattarli come thread-safe. Solo i documenti espliciti potrebbero dimostrarlo in "modo generale". Vedi "Concurrency In Java in pratica" per come trattare questi casi di mancanza di documenti. – Ruslan

4

Ho passato un'ora alla scoperta di fonti di com.sun.xml.internal.messaging.saaj (utilizzata come implementazione predefinita SAAJ in Oracle JDK) e ha scoperto che nessuna delle fabbriche restituito da WhateverFactory.newInstance() ha uno stato interno. Quindi sono sicuramente thread-safe e non richiedono di essere istanziati più volte.

Queste fabbriche sono:

Ad esempio, HttpSOAPConnectionFactory ha efficacemente solo 3 righe in corpo:

public class HttpSOAPConnectionFactory extends SOAPConnectionFactory { 

    public SOAPConnection createConnection() throws SOAPException { 
     return new HttpSOAPConnection(); 
    } 
} 

Che dire di SOAPMessage e SOAPConnection - devono essere utilizzati in un thread, sebbene le operazioni eseguite su di essi comportino diverse chiamate. (In realtà, è anche SOAPConnection#call() thread-safe poiché HttpSOAPConnection che non detiene alcun stato interiore, tranne closed variabile. Si può, ma non devono essere riutilizzati se non si garantisce che .close() non è mai invocato, altrimenti la successiva .call() getteranno.) Al termine dell'elaborazione, è necessario chiudere e dimenticare SOAPConnection e le istanze SOAPMessage utilizzate in particolare il ciclo di richiesta-risposta.

Per concludere: credo che tu faccia tutto correttamente tranne che per creare fabbriche separate per ogni chiamata. Almeno nell'implementazione menzionata queste fabbriche sono completamente prive di thread, quindi è possibile risparmiare sulle classi di caricamento.


Tutto detto si riferisce all'implementazione SAAJ predefinita fornita con Oracle JDK. Se si utilizza un application server Java EE commerciale (Websphere, JBoss ecc.) In cui l'implementazione può essere specifica del fornitore, è meglio indirizzare la domanda al proprio supporto.

+0

In ogni caso, non riesco ancora a capire la tua intenzione di utilizzare l'astrazione di CompletionService. Tutto ciò che fai può essere facilmente realizzato da 'Future <...> f = executor.submit (() -> {...})'. Tutti questi 'Future's che puoi raccogliere nella lista, poi prendi uno per uno e chiama' f.get() 'su ognuno per ottenere i risultati di ciascuna attività. 'Future # get()' restituisce garantito dopo che l'executor elabora l'attività e restituisce il risultato. –

+0

f.get() in un task potrebbe bloccare e impedirmi di richiamare f.get() sull'attività che è già stata completata. Ecco a cosa serve CompletionService. Mi dà i risultati al loro arrivo. – Ruslan

+0

@Ruslan si hai ragione. Ora capisco. Suppongo che tu abbia bisogno di loro ordinati. –

Problemi correlati