Ett tillståndslöst objekt har ingen som helst representation,
det innehåller till exempel inga fält. Här kommer ett exempel:
class StatelessAdder {
public int add(int a, int b) {
return a + b;
}
}
Det finns ingen risk med att flera trådar använder denna metod
samtidigt. Alla trådar kommer att använda sina egna värden
på inparametrarna (a och b) och få sin egen summa tillbaks.
public ImmutableAdder(int a) {
offset = a;
}
public int addOffset(int b) {
return offset + b;
}
}
Objektets tillstånd beskrivs av fältet offset, vilket
aldrig kan ändra värde eftersom det är konstant (deklarerat
som final int).
Observera dock att final inte nödvändigtvis innebär att ett fält är konstant. Om en referens till ett annat objekt är final innebär det bara att fältet alltid kommer att peka på samma objekt, det finns ingenting som förhindrar att det refererade objektet ändrar tillstånd. Antag att ett objekt representerar ett fönster på skärmen och att det har en final referens till ett annat objekt som innehåller förnstrets position på skärmen. Fönstrets position är i högsta grad en del av dess tillstånd och det är i detta fall inte alls säkert att låta flera trådar samtigt manipulera positionsobjektet trots att dess referens är final.
Det går heller inte att låta flera trådar samtidigt skriva till en double eller long även om den är final. Det beror på att skrivning av dessa typer sker i två steg, först ena halvan och sedan den andra. Om en tråd skriver ena halvan av värdet och sedan blir avbruten av en annan tråd som skriver den andra halvan kommer variabeln att innehålla hälften av två olika värden. Lösningen är att deklarera den volatile. I så fall sker alla operationer på variabeln atomärt, dvs de kan ej bli avbrutna.
Exempel på objekt med konstanta tillstånd är abstrakta datatyper som till exempel java.lang.Integer (representerar en int), java.lang.String (representerar en sträng) och java.awt.Color (representerar en färg).
Det som låses är alltså accessen till just det objekt
som anges vid synchronized-satsen. Om en metod eller ett fält
ligger i samma klass som synchronized-satsen eller i en superklass
spelar ingen roll, det är objektet som låses. Däremot
ligger inte statiska metoder och fält i något objekt och tillträdet
till dem påverkas inte av att ett objekt av klassen låses. Statiska
metoder låses genom att utnyttja låset i ett objekt av klassen
Class. Varje klass i Java representeras automatiskt av ett objekt
av klassen Class. Detta objekt kan refereras som MinLillaKlass.class.
De statiska medlemmarna i en klass kan alltså låsas genom att
skriva till exempel:
synchronized ( MinLillaKlass.class ) {
...
}
eller vid deklarationen av en statisk metod:
public static synchronized void enMetod() {
...
}
Ordet synchronized är inte en del av en metods signatur. En omdefinierad metod i en subklass blir alltså inte synchronized bara för att metoden i superklassen är det.
Synchronized och volatile påverkar även ett fälts synlighet. Värden som skrivs i ett synchronized-block eller till ett fält som är volatile måste vara synliga för andra trådar som går in i synchhronized-block eller använder samma volatile fält. Observera att om vi inte använder sycnhronized eller volatile finns det inga garantier om synlighet.
I ett fullständigt synkroniserat objekt (en komplett definition finns på sid 78) är alla metoder synchronized. Det får heller inte finnas några fält som är deklarerade public, därför att de skulle kunna ändras utan att gå in i någon av synkroniserade metoderna. Ett fullständigt synkroniserat objekt är garanterat trådsäkert, det finns aldrig någon risk för kapplöpning. Det är heller aldrig några problem att lägga till nya metoder. Så länge de är synchronized förblir objektet trådsäkert. Tyvärr blir ett sådant objekt långsamt att använda eftersom det tar mycket tid hantera låsen.
Här är ett exempel, LazySingletonCounter.java. Metoden instance() ser inte ut som på sid 85 i boken men funktionaliteten är exakt densamma. Observera först att all låsning sker med hjälp av objektet LazySingletonCounter.class. Det är för att det inte ska gå att samtidigt exekvera metoderna next() eller reset() som tillhör det enda objektet och metoden instance() som tillhör klassen. Tillvägagångssättet att inte initiera fältet s förrän det verkligen behövs, dvs första gången instance() anropas, kallas lazy initialization.
En intressant variant, StaticCounter.java, finns på sid 86. Här är alla fält och metoder statiska och konstruktorn private. Det är inte meningen att det någonsin ska skapas något objekt av klassen. Ett annat exempel på en sådan klass är java.lang.Math som innehåller metoder för matematiska beräkningar. Synkroniseringen blir väsentligt enklare än i det förgående fallet.
public void showNextPoint() {
Point p = new Point();
p.x = computeX();
p.y = computeY();
display(p);
}
protected void display(Point p) {
// somehow arrange to show
p.
}
}
I metoden showNextPoint() ovan lämnas objektet p ut
när display(p) anropas. Det är dock ofarligt eftersom det
aldrig mer används av showNextPoint(). Om det används av
någon annan tråd senare kommer den att vara ensam om det och
kapplöpning förekommer därför ändå inte. Skulle
det däremot vara nödvändigt att lämna ut objektet innan
showNextPoint() är slut, som i fallet nedan, finns det ändå
åtgärder för att slippa synkronisera.
public void showNextPoint() {
Point p = new Point();
p.x = computeX();
p.y = computeY();
doSometingWithP(p); /*
This line is new. */
display(p);
}
Vi kan lösa problemet med någon av de åtgärder som
beskrivs på sid 102. Till exempel kan vi skicka en kopia av objektet
om det som i detta fall inte är själva objektet som är intressant
utan bara värdet av dess fält. I sådana fall byter vi raden
doSometingWithP(p) ovan mot doSometingWithP(new Point(p.x, p.y)).
Om synkronisering ska implementeras i ett helt osynkroniserat objekt kan
det lösas genom att, enligt ovan, låta det osynkroniserade objektet
helt ägas av ett annat objekt där vi inför synkroniseringen.
Det synkroniserade objektet blir då en adapter.
Här kommer ett exempel där den osynkroniserade klassen UnsynchedPoint
ägs av adaptern SynchedPoint:
class UnsynchedPoint {
public double x;
public double y;
}
class SynchedPoint {
protected final UnsynchedPoint delegate = new UnsynchedPoint();
public synchronized double getX() { return delegate.x;}
public synchronized double getY() { return delegate.y;
}
public synchronized void setX(double v) { delegate.x
= v; }
public synchronized void setY(double v) { delegate.y
= v; }
}
Här kommer ett klassdiagram som beskriver ovanstående kod. Den
lilla "diamanten" på pilen betyder aggregering (se ovan).
En anledning till att använda en adapter på detta sätt kan vara att vi har behov av både en osynkroniserad Point och en synkroniserad. Med adaptern slipper vi kopiera all kod i Point till SynchedPoint.