Is deze code Double Checked Locking veilig?

Ik kijk naar een code in onze app waarvan ik denk dat er een geval van ' dubbel gecontroleerde vergrendeling optreedt ". Ik heb een voorbeeldcode geschreven die lijkt op wat we doen.

Kan iemand zien hoe dit dubbel-gecontroleerd vergrendelen kan ervaren? Of is dit veilig?

class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        Helper result;
        synchronized(this) {
            result = helper;
        }

        if (helper == null) {
            synchronized(this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }
}

Base-code geleend van wiki .

2
ja, dit is dubbel gecontroleerd vergrendelen
toegevoegd de auteur Dmitry B., de bron
Dit spul is moeilijk om goed te krijgen en gemakkelijk in de war te brengen. U kunt Guava's Suppliers.memoize (leverancier) gebruiken, die dit voor u implementeert!
toegevoegd de auteur Ray, de bron
@Aishwar, je zou waarschijnlijk de Wikipedia-pagina moeten lezen waar je naar linkt in meer details, in het bijzonder wat het zegt over volatile : gebruik het (of gebruik geen DCL).
toegevoegd de auteur Bruno, de bron
Waarom declareer je resultaat en gebruik je het dan niet?
toegevoegd de auteur Kirk Woll, de bron
Het is een beetje zinloos gezien de methode altijd het slot krijgt.
toegevoegd de auteur Tom Hawtin - tackline, de bron
er is GEEN dubbele controle, het hele idee is het twistpunt overslaan, d.w.z. het slot verkrijgen. In dat geval is @Tom absoluut correct
toegevoegd de auteur bestsss, de bron
@KirkWoll Mijn begrip is hier misschien verkeerd. Maar ik stelde me voor dat dit is wat er gebeurt. Er is een vergrendeling op this tijdens het toewijzen van helper . En er is nog een slot aan het begin van de functie getHelper op this . Dus als getHelper wordt aangeroepen op Thread 1 wanneer een toewijzing aan de gang is in Thread 2, bevat het eerste gesynchroniseerde -blok de uitvoering van Thread 1 totdat de toewijzing op Thread 2 is voltooid . Dus de waarde van helper is nooit toegankelijk tijdens het instellen.
toegevoegd de auteur Aishwar, de bron
@ TomHawtin-tackline Ik ben het ermee eens dat dit misschien niet efficiënt is, maar ik heb een aantal bestaande code gemodelleerd en probeer alleen te analyseren of dit wordt beïnvloed door het probleem double checked-locking .
toegevoegd de auteur Aishwar, de bron

3 antwoord

Het is onnodig ingewikkeld, de eenvoudigste 'veilige' manier om DCL te doen is als volgt:

class Foo {
  private volatile Helper helper = null;
  private final Object mutex = new Object(); 
  public Helper getHelper() {
    if (helper == null) {
        synchronized(mutex) {
            if (helper == null) {
                helper = new Helper();
            }
        }
    }
    return helper;
  }
}

De belangrijkste punten zijn:

  • In het 'gelukkige' geval verwachten we dat helper al is toegewezen, dus als dit het geval is, kunnen we het gewoon retourneren zonder een gesynchroniseerd blok in te voeren.
  • Helper is gemarkeerd als vluchtig om de compiler te laten weten dat helper op elk moment kan worden gelezen van/geschreven door elke thread en het is belangrijk dat lezen/schrijven niet opnieuw worden besteld.
  • Het gesynchroniseerde blok gebruikt een privéeindvariabele om te synchroniseren om een ​​potentiële prestatieslag te voorkomen in het geval van een ander gebied van codesynchronisatie op de instantie dit .
6
toegevoegd
U zou helper moeten toewijzen aan een lokale variabele.
toegevoegd de auteur bestsss, de bron

Het hele punt van dubbel gecontroleerd vergrendelen is dat het snelle pad (wanneer u het object niet hoeft te instantiëren) niet gesynchroniseerd is. Dus wat je hebt is niet dubbel gecontroleerd vergrendelen.

U moet het eerste gesynchroniseerde blok verwijderen om een ​​gebroken dubbel gecontroleerd slot te krijgen. Vervolgens moet u helper wijzigen om het te verhelpen.

3
toegevoegd
en dit is het beste antwoord.
toegevoegd de auteur bestsss, de bron
Bedankt. Dit beantwoordt mijn vraag. Het verklaart waarom de bovenstaande code niet dubbel gecontroleerd is.
toegevoegd de auteur Aishwar, de bron

Dit onderdeel is een standaard dubbel gecontroleerde sluiting:

if (helper == null) {
    synchronized(this) {
        if (helper == null) {
            helper = new Helper();
        }
    }
}

Het eerste deel is een nutteloze toewijzing die niets doet aan het dubbel gecontroleerde vergrendelingsonderdeel: als helper null is, wordt het toch uitgevoerd, als het dat niet is, zou het toch niet worden uitgevoerd. Het is volledig ineffectief.

0
toegevoegd
Alle andere dingen hetzelfde zijn, dat is verkeerd.
toegevoegd de auteur Tom Hawtin - tackline, de bron
@Viruzzo Mijn begrip is hier misschien verkeerd. Maar ik stelde me voor dat dit is wat er gebeurt. Er zit een slot op deze terwijl je een helper toewijst. En er is nog een slot aan het begin van de getHelper-functie. Dus als getHelper wordt aangeroepen op Thread 1 wanneer een opdracht in Thread 2 aan de gang is, houdt het eerste gesynchroniseerde blok de uitvoering van Thread 1 vast totdat de toewijzing op Thread 2 is voltooid. Dus de waarde van helper is nooit toegankelijk tijdens het instellen.
toegevoegd de auteur Aishwar, de bron