39 votes

Synchronisation sur les variables locales

Aujourd'hui, j'ai été confronté à la méthode constructServiceUrl() de la org.jasig.cas.client.util.CommonUtils classe. Je l'ai trouvé très étrange :

final StringBuffer buffer = new StringBuffer();

synchronized (buffer)
{
    if (!serverName.startsWith("https://") && !serverName.startsWith("http://"))
    {
        buffer.append(request.isSecure() ? "https://" : "http://");
    }

    buffer.append(serverName);
    buffer.append(request.getRequestURI());

    if (CommonUtils.isNotBlank(request.getQueryString()))
    {
        final int location = request.getQueryString().indexOf(
                artifactParameterName + "=");

        if (location == 0)
        {
            final String returnValue = encode ? response.encodeURL(buffer.toString()) : buffer.toString();

            if (LOG.isDebugEnabled())
            {
                LOG.debug("serviceUrl generated: " + returnValue);
            }

            return returnValue;
        }

        buffer.append("?");

        if (location == -1)
        {
            buffer.append(request.getQueryString());
        }
        else if (location > 0)
        {
            final int actualLocation = request.getQueryString()
                    .indexOf("&" + artifactParameterName + "=");

            if (actualLocation == -1)
            {
                buffer.append(request.getQueryString());
            }
            else if (actualLocation > 0)
            {
                buffer.append(request.getQueryString().substring(0, actualLocation));
            }
        }
    }
}

Pourquoi l'auteur a-t-il synchronisé une variable locale ?

72voto

dogbane Points 85749

Il s'agit d'un exemple de manuel " rugosité de la serrure "et peut avoir été fait pour obtenir une augmentation des performances.

Considérez ces deux extraits :

StringBuffer b = new StringBuffer();
for(int i = 0 ; i < 100; i++){
    b.append(i);
}

contre :

StringBuffer b = new StringBuffer();
synchronized(b){
  for(int i = 0 ; i < 100; i++){
     b.append(i);
  }
}

Dans le premier cas, le StringBuffer doit acquérir et libérer un verrou 100 fois (parce que append est une méthode synchronisée), alors que dans le second cas, le verrou est acquis et libéré une seule fois. Cela peut vous donner un gain de performance et c'est probablement la raison pour laquelle l'auteur l'a fait. Dans certains cas, le compilateur peut effectuer cette opération rugosité de la serrure pour vous (mais pas autour des constructions en boucle car vous pourriez finir par retenir un verrou pendant de longues périodes).

À propos, le compilateur peut détecter qu'un objet ne "s'échappe" pas d'une méthode et ainsi supprimer complètement l'acquisition et la libération de verrous sur l'objet (élision de verrous) puisqu'aucun autre thread ne peut accéder à l'objet de toute façon. De nombreux travaux ont été réalisés sur ce sujet dans le cadre du projet JDK7 .


Mise à jour :

J'ai effectué deux tests rapides :

1) SANS ÉCHAUFFEMENT :

Dans ce test, je n'ai pas exécuté les méthodes plusieurs fois pour "chauffer" la JVM. Cela signifie que le compilateur du serveur Java Hotspot n'a pas eu l'occasion d'optimiser le code, par exemple en éliminant les verrous pour les objets d'échappement.

JDK                1.4.2_19    1.5.0_21    1.6.0_21    1.7.0_06
WITH-SYNC (ms)         3172        1108        3822        2786
WITHOUT-SYNC (ms)      3660         801         509         763
STRINGBUILDER (ms)      N/A         450         434         475

Avec le JDK 1.4, le code avec le bloc synchronisé externe est plus rapide. Cependant, avec le JDK 5 et plus, le code sans synchronisation externe l'emporte.

2) AVEC ÉCHAUFFEMENT :

Dans ce test, les méthodes ont été exécutées plusieurs fois avant que les temps ne soient calculés. Ceci a été fait afin que la JVM puisse optimiser le code en effectuant une analyse d'échappement.

JDK                1.4.2_19    1.5.0_21    1.6.0_21    1.7.0_06
WITH-SYNC (ms)         3190         614         565         587
WITHOUT-SYNC (ms)      3593         779         563         610
STRINGBUILDER (ms)      N/A         450         434         475

Une fois aOgnacien ,a gwaiitnh, JwDOiKnt ch1e . J4aD,gK a ti1hn.e,4 ,cw oitdthehe wJciDotKdh e 1 t.wh4ie,t het xhttehe erc noeadxlet eswryinntachlh rtsohyneni czehexrdto enbrilnzoaecldk sbiylsno ccfhkar soitnsei rzf. ea dsH tobewlreo.vc ekHr o,iw sew vifetarhs, t JewDriK. t h5H oJawDneKdv e5ar b,ao nvwdei ,ta hbb ooJvtDehK, m5be otathnhod d msae btpohevoredf,so rbpmoe trehfq oumraemlt lheyoq duwsae llplley.r fwoerlml .eOqnucael layg awienl,l .avec JDK 1.4, le code avec le bloc synchronisé externe est plus rapide. Cependant, avec le JDK 5 et plus, les deux méthodes sont aussi performantes l'une que l'autre.

Voici ma classe test (n'hésitez pas à l'améliorer) :

public class StringBufferTest {

    public static void unsync() {
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < 9999999; i++) {
            buffer.append(i);
            buffer.delete(0, buffer.length() - 1);
        }

    }

    public static void sync() {
        StringBuffer buffer = new StringBuffer();
        synchronized (buffer) {
            for (int i = 0; i < 9999999; i++) {
                buffer.append(i);
                buffer.delete(0, buffer.length() - 1);
            }
        }
    }

    public static void sb() {
        StringBuilder buffer = new StringBuilder();
        synchronized (buffer) {
            for (int i = 0; i < 9999999; i++) {
                buffer.append(i);
                buffer.delete(0, buffer.length() - 1);
            }
        }
    }    

    public static void main(String[] args) {

        System.out.println(System.getProperty("java.version"));

        // warm up
        for(int i = 0 ; i < 10 ; i++){
            unsync();
            sync();
            sb();
        }

        long start = System.currentTimeMillis();
        unsync();
        long end = System.currentTimeMillis();
        long duration = end - start;
        System.out.println("Unsync: " + duration);

        start = System.currentTimeMillis();
        sync();
        end = System.currentTimeMillis();
        duration = end - start;
        System.out.println("sync: " + duration);

        start = System.currentTimeMillis();
        sb();
        end = System.currentTimeMillis();
        duration = end - start;
        System.out.println("sb: " + duration);  
    }
}

7voto

Andrzej Doyle Points 52541

Inexpérience, incompétence, ou plus probablement code mort mais bénin qui reste après le remaniement.

Vous avez raison de vous interroger sur l'intérêt de cette démarche. Les compilateurs modernes utiliseront l'analyse d'échappement pour déterminer que l'objet en question ne peut pas être référencé par un autre thread, et donc élideront (supprimeront) complètement la synchronisation.

(Dans un sens plus large, c'est parfois Il n'est pas utile de synchroniser sur une variable locale - ce sont toujours des objets après tout, et un autre thread peut toujours avoir une référence à eux (tant qu'ils ont été en quelque sorte "publiés" après leur création). Néanmoins, c'est rarement une bonne idée car c'est souvent peu clair et très difficile à mettre en place - un mécanisme de verrouillage plus explicite avec d'autres threads s'avérera probablement meilleur dans l'ensemble dans ces cas).

5voto

finnw Points 24592

Je ne pense pas que la synchronisation puisse avoir un quelconque effet, puisque buffer n'est jamais transmise à une autre méthode ou stockée dans un champ avant de sortir de sa portée, de sorte qu'aucun autre thread ne peut y avoir accès.

La raison pour laquelle il est là pourrait être politique - j'ai été dans une situation similaire : Un "patron aux cheveux pointus" insistait pour que je clone une chaîne de caractères dans une méthode setter au lieu de simplement stocker la référence, de peur de voir le contenu modifié. Il n'a pas nié que les chaînes de caractères sont immuables mais a insisté pour le cloner "juste au cas où". Comme c'était inoffensif (tout comme cette synchronisation), je n'ai pas discuté.

3voto

stevevls Points 6590

C'est un peu fou... ça ne fait rien à part ajouter des frais généraux. Sans mentionner que les appels à StringBuffer sont déjà synchronisés, c'est pourquoi StringBuilder est préféré pour les cas où vous n'aurez pas plusieurs threads accédant à la même instance.

1voto

king_nak Points 4093

IMO, il n'y a pas besoin de synchroniser cette variable locale. Ce n'est que si elle était exposée à d'autres, par exemple en la passant à une fonction qui la stockera et l'utilisera potentiellement dans un autre thread, que la synchronisation aurait un sens.
Mais comme ce n'est pas le cas, je n'en vois pas l'utilité.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X