Java >> Java opplæring >  >> Java

Synkroniserer på en heltallsverdi

Du vil virkelig ikke synkronisere på en Integer , siden du ikke har kontroll over hvilke forekomster som er like og hvilke forekomster som er forskjellige. Java gir bare ikke en slik funksjon (med mindre du bruker heltall i et lite område) som er pålitelig på tvers av forskjellige JVM-er. Hvis du virkelig må synkronisere på et heltall, må du beholde et kart eller sett med heltall slik at du kan garantere at du får den eksakte forekomsten du ønsker.

Bedre ville være å lage et nytt objekt, kanskje lagret i en HashMap som tastes inn av Integer , for å synkronisere på. Noe sånt som dette:

public Page getPage(Integer id) {
  Page p = cache.get(id);
  if (p == null) {
    synchronized (getCacheSyncObject(id)) {
      p = getFromDataBase(id);
      cache.store(p);
    }
  }
}

private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>();

private Object getCacheSyncObject(final Integer id) {
  locks.putIfAbsent(id, id);
  return locks.get(id);
}

For å forklare denne koden bruker den ConcurrentMap , som tillater bruk av putIfAbsent . Du kan gjøre dette:

  locks.putIfAbsent(id, new Object());

men da pådrar du deg den (lille) kostnaden ved å lage et objekt for hver tilgang. For å unngå det lagrer jeg bare heltallet i Map . Hva oppnår dette? Hvorfor er dette annerledes enn bare å bruke selve heltallet?

Når du gjør en get() fra en Map , nøklene sammenlignes med equals() (eller i det minste metoden som brukes tilsvarer å bruke equals() ). To forskjellige Integer-forekomster av samme verdi vil være lik hverandre. Dermed kan du sende et hvilket som helst antall forskjellige heltallsforekomster av "new Integer(5) " som parameter til getCacheSyncObject og du vil alltid få tilbake bare den aller første forekomsten som ble sendt inn som inneholdt den verdien.

Det er grunner til at du kanskje ikke vil synkronisere på Integer ... du kan havne i vranglås hvis flere tråder synkroniseres på Integer objekter og bruker dermed uforvarende de samme låsene når de ønsker å bruke forskjellige låser. Du kan fikse denne risikoen ved å bruke

  locks.putIfAbsent(id, new Object());

versjon og dermed pådra seg en (veldig) liten kostnad for hver tilgang til cachen. Hvis du gjør dette, garanterer du at denne klassen vil synkronisere på et objekt som ingen annen klasse vil synkronisere på. Alltid en god ting.


Integer.valueOf() returnerer bare bufrede forekomster for et begrenset område. Du har ikke spesifisert rekkevidden, men generelt vil dette ikke fungere.

Jeg vil imidlertid sterkt anbefale deg å ikke ta denne tilnærmingen, selv om verdiene dine er i riktig område. Siden disse bufret Integer forekomster er tilgjengelige for enhver kode, kan du ikke kontrollere synkroniseringen fullt ut, noe som kan føre til en vranglås. Dette er det samme problemet som folk har forsøkt å låse på resultatet av String.intern() .

Den beste låsen er en privat variabel. Siden bare koden din kan referere til den, kan du garantere at ingen vranglåser vil oppstå.

Forresten, ved å bruke en WeakHashMap vil ikke fungere heller. Hvis forekomsten som fungerer som nøkkel, ikke er referert, vil den bli samlet inn søppel. Og hvis det er sterkt referert, kan du bruke det direkte.


Bruk et trådsikkert kart, for eksempel ConcurrentHashMap . Dette vil tillate deg å manipulere et kart trygt, men bruk en annen lås for å gjøre den virkelige beregningen. På denne måten kan du ha flere beregninger som kjører samtidig med et enkelt kart.

Bruk ConcurrentMap.putIfAbsent , men i stedet for å plassere den faktiske verdien, bruk en Future med beregningsmessig lett konstruksjon i stedet. Muligens FutureTask gjennomføring. Kjør beregningen og deretter get resultatet, som vil trådsikkert blokkere til det er ferdig.


Java Tag