1. Vad är låsning?
En tråd har låst ett objekt om alla andra trådar tvingas att vänta då de försöker låsa objektet.
2. Behovet av låsning
Man måste låsa ett objekt i dessa fall:
- Om man har en delad variabel i objektet som flera trådar läser och skriver.
- Om objektet hanterar en delad resurs som man vill att endast en tråd åt gången ska få använda.
3. Huvudminne och arbetsminne
Nu kommer några nya begrepp lanseras:
- Huvudvariabler: Alla medlemsvariabler ( klassvariabler, instansvariabler och arraykomponenter) lagras i huvudminnet och delas av alla trådar. Dessa variabler kallas huvudvariabler.
- Arbetskopior: Varje tråd har ett arbetsminne där den lagrar lokala variabler och kopior av huvudvariablerna. Dessa kopior kallas för arbetskopior. En tråd kan aldrig komma åt en annan tråds arbetsminne. När en tråd exekverar arbetar den med sina egna arbetskopior.
- Låsning: Det går bara att låsa ett helt objekt, med antingen statisk låsning eller instanslåsning. Endast den tråd som låser objektet kan läsa och skriva till de låsta orginalvariablerna.
Om ingen låsning sker får vilken tråd som helst läsa och skriva till objektets huvudvariabler.
4. Låsning med monitorer
Nedan beskrivs hur en monitor sköter låsning:
- Varje objekt har en monitor som övervakare. Det enda sättet för en tråd att få tillgång till ett objekt är via monitorn.
- Monitorerna är globala, alltså delar alla trådar på monitorerna.
- Om en tråd, tråd1, vill använda ett objekt, objekt1, som inte är låst får tråd1 låset till objekt1.
- Nu när monitorn har låst objekt1 tillåts ingen annan tråd att låsa objekt1. De trådar som försöker blockeras.
- Om tråd1 stöter på ett anrop till en metod i ett annat objekt, objekt2, hoppar tråden till objekt2 men behåller samtidigt låset på objket1. Java använder sig alltså av slutna anrop (closed calls). När tråd1 är färdig i objekt2 fortsätter den att exekvera metoden i objekt1.
- När tråd1 är färdig med objekt1 lämnar den tillbaka låset till monitorn. Nästa tråd som frågar efter objekt1 kommer att få låset till objektet.
Som synes sker låsning helt automatiskt av monitorn, det enda man behöver göra är att tala om för monitorn vilka objekt som behöver skyddas och i vilka fall detta ska ske. Genom att deklarera en metod eller ett block som synchronized säger man till monitorn att låsa objektet då någon tråd går in i metoden.
5. synchronized
Nedan beskrivs hur nyckelordet synchronized fungerar i Java. Vi börjar med begreppet synchronized-block. Ett synchronized-block kan skapas på två sätt:
- Genom att deklarera ett block som synchronized.
- Genom att deklarera en metod som synchronized.
Att deklarerar ett block som synchronized ser ut såhär:
synchronized(Obj){
// something..
}
|
Obj är vanligen objektet självt, dvs this. Nedan försöker jag sammanfatta reglerna för låsning:
- En tråd som går in ett synchronized-block låser objektet.
- Ingen annan tråd kan låsa objektet förän objektet är upplåst igen.
- En tråd kan låsa flera objekt samtidigt. Detta sker om tråden befinner sig i en synchronized-metod som anropar en synchronized-metod i ett annat objekt.
- En tråd kan ha flera lås på samma objekt samtidigt.
- En tråd som går ur ett synchronized-block låser upp det låset.
- Om den tråd som har låst ett objekt dör låser JVM upp objektet.
- En statiskt synchronized-metod utför en statisk låsning av objektet, dvs en låsning som bara omfattar klassvariablerna.
- En instansmetod som är synchronized utför en instanslåsning av objektet, dvs låser bara instansvariablerna.
- Om en tråd vill använda ett låst objekt måste denna tråd snällt vänta på sin tur. Många trådar kan vänta på samma objekt.
- Om du väljer att överrida en metod som är deklarerad synchronized behöver metoden i subklassen inte vara synchronized.
6. Readers/Writers
Genom att tillämpa våra nya kunskaper kan vi modifiera klassen Controller i exemplet som beskrevs på sidan
Readers/Writers . Den nya kontroller ser ut såhär:
// CONTROLLER
// This is the class we will modify
// to solve the readers/writers problem
class Controller{
Resource res;
public Controller(Resource resource){
this.res=resource;
}
public synchronized void read(UserThread t){
res.read(t);
}
public synchronized void write(UserThread t){
res.write(t);
}
}
|
Det enda vi har gjort är att göra metoderna synchronized.
- OBS: Genom att göra både read() och write() synchronized kan vi förhindra att någon tråd anropar read() när write() arbetar. Metoderna låser nämligen hela objektet då de aktiveras, och detta blockerar alla trådar som försöker köra vilket som helst av objektets synchronized-block.
Nedan visas en körning av programmet med gjorda förändringar:
[olle@dev1]$ java ReadersWriters 3 3 3
All readers and writers are created!
|R |
| |
|R |
| |
|R |
| |
|W |
| |
|W |
| |
|W |
| |
|R |
| |
|R |
| |
|W |
| |
|R |
| |
|W |
| |
|W |
| |
|R |
| |
|R |
| |
|W |
| |
|R |
| |
|W |
| |
|W |
| |
|
Vi ser att vi har lyckats begränsa trådarna så att endast en tråd i taget har tillgång till Resource. Som programmet fungerar nu är skrivar-tråden ensam om objektet då den använder Resource.write(), detta är bra. Men tyvärr gäller samma sak för läsarna. Endast en läsare i taget har tillgång till Resource.read(). För att lösa problemet readers/writers måste vi låta läsarna anropa Resource.read() parallellt.