Kurt Nørmark ©
Department of Computer Science, Aalborg University, Denmark
September 2001
Abstract Previous lecture Next lecture Index References Contents | Programmer med samtidighed er et stort og vigtigt emne. I denne lektion introducerer og motiverer vi først emnet i forhold til den slags programmering, vi hidtil har studeret. Dernæst ser vi på et antal vigtige og klassiske problemstillinger i forbindelse med samtidighed. En væsentlig årsag til at dyrke emnet på dette kursus skal tilskrives Java's understøttelse af samtidighed. I Java taler man om 'tråde'. Vi gennemgår i denne lektion Java's forskellige muligheder for programmering af flere samtidige tråde inden for rammerne af ét program. Vi ser derimod ikke på samtidighed, som udspiller sig mellem to eller flere Java programmer, som kører på forskellige maskiner. |
Introduktion og motivation |
Vores programmering indtil nu... Slide Note Contents Index References | Lad os starte med at karakterisere den slags programmering, vi har bedrevet indtil vi nu i denne lektion bliver introduceret for mulighederne for samtidighed |
Når vi programmerer er vi vant til at skulle fastlægge rækkefølgen af alle de handlinger, vi foreskriver. Ofte er rækkefølgen væsentlig. Men til andre tider er rækkefølgen ligegyldig. Når sidstnævnte er tilfældet åbner vi i mange tilfælde op for betydelig bedre programmer ved ikke at insistere på en bestemt rækkefølge af de foreskrevne handlinger. Denne observation er en passende måde at starte dagens emne, som vi har valgt at kalde samtidighed |
|
The concept sekventielt programforløb: Et sekventielt programforløb udfører beregningsenheder i en bestemt rækkefølge. Én beregningsenhed starter først når den forrige er afsluttet | I et sekventielt programforløb, eller blot i et sekventielt program, udføres de enkelt beregningsenheder i en rækkefølge, som er bestemt af programmets kontrolstrukturer. Beregningsenhederne er primært kommandoer, men også de enkelte dele af udtryk bør i denne sammenhæng betegnes som beregningsenheder |
|
|
Naturlig samtidighed Slide Note Contents Index References | Det er velkendt fra utallige hverdagssituationer at ting foregår samtidigt. Dette 'fact of life' har vi naturligvis lyst til at kunne overføre til de handlinger, som foreskrives i de programmer, vi laver. Dette fænomen vil vi her diskutere under overskriften 'naturlig samtidighed'. |
|
Som et eksempel på samtidighed fra vores hverdag kan vi tænke på handlinger, som udføres af to eller flere personer. Hvis vi ønsker at simulere dette på en computer i et sekventielt programforløb kan dette blive ganske svært. Vi vil måske i en første simpel løsning skiftevis simulere en handlig fra den ene person efterfulgt af en handling fra den anden person. I en mere realistisk simulering bliver vi nødt til at tage hensyn til, at den ene person kan udføre mange handlinger i et tidrum, hvor den anden person er inaktiv. Det vil være særdeles ønskværdigt at kunne modellere de to personers samtidige handlinger mere naturligt i et program. |
|
Afgrænsning Slide Note Contents Index References | Da samtidighedsproblematikken er stor og bred vil vi her afgrænse os til at se på et bestemt, og relativt snævert aspekt |
|
|
|
Terminologi Slide Note Contents Index References | Vi vil ikke her gå i pedantiske detaljer om terminologi, men blot bemærke at også inden for dette område bruges der mange forskellige ord |
|
Table. En række ord på dansk og engelsk som benyttes når vi interesserer os for samtidighed |
|
Begreber og problemstillinger |
Grundliggende antagelser Slide Note Contents Index References | Vi starter denne afdeling med en meget væsentlig antagelse, som på en grundliggende måde vil påvirke vores programmering med samtidige forløb |
|
|
|
Oversigt over væsentlige problemstillinger Slide Note Contents Index References | Vi vil også her på dette tidlige sted i lektionen benytte lejligheden til at give en samlet oversigt over nogle af de væsentlige problemstillinger vi møder, når interessen samler sig om samtidighed |
|
|
Samtidighed i forhold til OOP Slide Note Contents Index References | Nøgleordet, som vi nu introducerer er aktive objekter. Aktive objekter kommer til at danne kontrast til de objekter vi indtil nu har studeret, og som vi naturligt her vil kalde passive objekter |
|
The concept aktivt objekt: Et aktivt objekt er et objekt, der ud over variable og metoder har sig eget selvstændige og sekventielle programforløb | Et aktivt objekt udviser selvstændig adfærd på en sådan måde, at objektet kan ændre tilstand uden at det bliver påvirket gennem dets klientgrænseflade. En aktivt objekt er udgangspunktet for et selvstændigt programforløb. I billedsprog kan vi sige, at dette programforløb giver objektet et selvstændigt liv | |
The concept passivt objekt: Et passivt objekt er et objekt med variable og metoder, som kun kan ændre tilstand som følge af påvirkninger fra andre objekter | Som en kontrast til aktive objekter vil vi tale om passive objekter. Et passivt objekt er altså et ikke-aktivt objekt. Et passivt objekt kan ikke ændre tilstand 'af sig selv', men kun gennem påvirkninger fra andre objekter (som måske er påvirket af tredje objekter, som måske ultimativt er aktive objekter) |
|
Tråde i Java |
Trådbegrebet i Java Slide Note Contents Index References | Inden vi rigtig går igang med at rulle Java's understøttelse af tråde ud vil vi karakterisere Java's tråd begreb |
The concept tråd: En tråd er et sekventielt programforløb i et Java program, som tager udgangspunkt i et objekt af typen Thread | En tråd er betegnelsen for et sekventielt programforløb inden for rammerne af et Java program |
|
Interfacet Runnable Slide Note Contents Index References | Metoden run, specificeret i interfacet Runnable, er udgangspunktet for tråde i Java |
|
|
|
Trådskabelse gennem subklasse af Thread Slide Note Contents Index References | Vi ser først på hvordan vi laver en ny tråd i et Java program ved at at lave en subklasse af klassen Thread, som redefinerer metoden run |
|
Program: En subklasse af Thread samt start af en tråd med metoden start.
|
|
|
|
Trådskabelse ved implementering af Runnable Slide Note Contents Index References | Som et alternativ kan man lave sin egen klasse som implementerer interfacet Runnable. En instans af denne klasse skal dog overgives til en instans af Thread for at kunne virke som en tråd i et Java program |
|
Program: Installering af af en 'Runnable objekt' i et Thread objekt'.
Læg mærke til at instansen af SecondThread overføres som den første parameter til Thread konstruktoren.
Den pågældende Thread constructor vil aktivtere run i Runnable objektet i stedet for at kalde
run i thread objektet. Slå selv op i Threads konstruktor dokumentation, og bliv overbevist.
|
|
|
Egenskaber af klassen Thread Slide Note Contents Index References | Vi vil her se nærmere på egenskaberne af klassen Thread |
Thread navnet bruges til at identificere en tråd, f.eks. i udskrifter o.l. |
|
Eksempel på et program med tråde: Skjald Slide Note Contents Index References | Lad os nu se på vores første egentlige program med flere tråde. Eksemplet viser en 'skjald' som synger en sang. Samtidigheden kommer ind i billedet når to skjalde synger hver sin sang |
Program: Klassen Skjald som arver fra Thread.
Skjald starter en tråd, som synger sine strofer et antal gange via metoden syngVers.
Et vers består altså af et antal strofer (linier) i en bestemt rækkefølge.
Når et vers synges holdes der en pause efter afsyngningen af hver strofe.
Pausen's længde er bestemt af instansvariablen forsinkelse. Pausen realiseres ved
at tråden sover et tidsrum, som er bestemt af en parameter til konstruktoren. Når
en tråd sover kan Java's køretidssystem benytte lejligheden til at give CPU'en til en anden
tråd, som kan udføres
|
|
Program: Klassen Sangkor, som starter to aktive Skjald objekter.
Hver skjald synger en af vort land's elskede sange, hhv. 'Mester Jakob' og 'Glade jul'.
Bemærk at pauserne mellem stroferne i 'Mester Jakob' er væsentlig kortere end
i 'Glade jul' (som jo bør afsynges i 'salmetempo').
|
|
Program: Det samlede program med klasserne Skjald og Sangkor.
| ![]() |
Program: Muligt output fra ovenstående program.
Vi siger 'muligt output', idet andre skeduleringer ikke kan udelukkes (f.eks. på en anden platform,
som implementerer en anden skeduleringsalgoritme)
|
|
Mulige tilstande af en tråd i Java Slide Note Contents Index References | En tråd kan være i flere forskellige tilstande. Trådens tilstand er af afgørende betydning for skeduleringen af tråde i et Java system. Skedulering behandles længere fremme i denne lektion |
|
Figure. Et tilstandsdiagram som viser at en tråd kan være i én af fire mulige tilstande. Endvidere vises de 'transitioner' (begivenheder) som bringer en tråd fra én tilstand til en anden. Denne figur er tilpasset fra en tilsvarende figur i bogen 'Core Java', volume 2 | ![]() |
Beskrivelse af betingelsen for at være i live er fortolket ud fra beskrivelsen af metoden isAlive i klassen Thread, jf. hosstående reference |
|
Tråde i forhold til Swing Slide Note Contents Index References | Vi vil her kort indskyde nogle bemærkninger om tråde i Swing |
|
|
|
Skedulering af tråde i Java |
Indbyrdes fremdrift i tråde Slide Note Contents Index References | Vi vil nu se på de mekanismer, som afgør hvilken tråd, der bliver udført på computeren på det givet tidspunkt |
|
The concept skedulering: Kontrollen af den tidslige og indbyrdes ordning af en mængde handlinger i forskellige tråde kaldes skedulering | Skedulering betyder at arrangere eller kontrollere en mængde handlinger i forhold til hinanden i tidslig rækkefølge. I forhold til samtidighed indebærer skedulering beslutningen om hvilken tråd der skal udføres på computerens CPU på et bestemt tidspunkt |
|
|
Skedulering baseret på prioriteter i Java Slide Note Contents Index References | Vi ser nu på detaljerne i Java's skeduleringsalgoritme |
At være 'preempting' betyder direkte oversat at 'tage selv forud for andre'. I ovenstående er betydningen, at en tråd, som på en eller anden måde får højere prioritet end alle andre, umiddelbart overtager CPU'en |
I klassen Thread findes en række simple metoder, som ændrer på prioriteterne af en tråd. Disse er setPriority og getPriority |
|
Eksempel på prioriteter: Prioriterede sange Slide Note Contents Index References | Vi forsøger nu på en konkret måde at illustrerer nogle af ovenstående prioritetsforhold ved at arbejde videre på Skjald eksemplet |
|
Program: Klassen Skjald hvor et vers synges uden 'sleep' mellem stroferne.
Efter et vers er der mulighed for en pause, hvis forsinkelse er sat til et positivt heltal
|
|
Program: Klassen Sangkor, som starter to forskelligt prioriterede skjalde (tråde).
Skjalden som synger 'Glade Jul' er højere prioriteret end skjalden, der synger 'Mester Jakob'.
På grund af skeduleringsreglerne i Java bliver alle 3 vers af 'Glade Jul' derfor sunget før
de 3 vers af 'Mester Jakob', jf. nedenstående output af programmet
|
|
Program: Det samlede program med klasserne Skjald og Sangkor.
| ![]() |
Program: Muligt output fra ovenstående program.
Vi siger igen 'muligt output', idet andre skeduleringer stadigvæk ikke kan udelukkes
|
|
Skulle det ske (en særlig dag, på et obskurt Java system, ...) at den lavere prioriterede tråd fik køretid
på bekostning af den højere prioriterede tråd ville dette ikke være at opfatte som en fejl |
|
Tommelfingerregler for prioritering Slide Note Contents Index References |
|
|
Synkronisering af tråde i Java |
Interferens mellem tråde Slide Note Contents Index References | Vi vil her studere det forhold, at to tråde let kan komme til at 'træde hinanden over tæerne' |
|
Figure. Illustration af situationen hvor én konto opdateres samtidig af to forskellige tråde. Hvis der indledningsvist er 100 kroner på kontoen vil der efter de to samtidige opdateringer være 5100 kroner på kontoen. Den grønne opdatering med de 1000 kroner går tabt. Dette er klart en fejl, som ikke kan tolereres | ![]() |
|
Kritiske regioner Slide Note Contents Index References | Første skridt på vej mod kontrol af utilsigtet interferens mellem tråde består i at sikre mulighed for udelelig adgang til fælles ressourcer. Det relevante begreb til dette er en 'kritisk region' |
|
The concept kritisk region: En kritisk region er en sekvens af kommandoer som kan udføres af højst én tråd ad gangen | For at sikre os mod uheldig interferens definerer vi en kritisk region som et område af et program, hvori højst én tråd må befinde sig ad gangen |
Synkroniserede metoder Slide Note Contents Index References |
|
Syntax: Syntaks for en synkroniseret metode definition. Hvis en tråd aktiverer en synkroniseret metode låses objektet for andre tråde, således at disse ikke kan udføre metoder på objektet. Ovenstående syntaks viser eksplicit, at vi benytter nøgleordet 'synchronized' som modifier. Ret beset er nøgleordet synchronized en del af metoden's egenskabsliste, side om side med egenskaber så som private og static |
|
|
Eksempel på synkronisering: SynkroniseretKonto Slide Note Contents Index References | Som et eksempel på ovenstående vil vi studerende en konto med synkroniserede operationer |
|
Program: Java klassen SynkroniseretKonto.
Alle operationer som aflæser og modificerer egenskaber ved en konto låser nu konto objektet
|
|
Program: En bank som opstarter to filialer der gennemfører ens transaktioner.
Vi benytter her en ganske kompakt måde at definere de to banktråde. I en mere
realistisk situation kunne man definere en klasse Filial, som får adgang til en række fælles
konto objekter.
|
|
|
Program: Det samlede program med SynkroniseretKonto og filial opstart.
| ![]() |
Exercise 14.1. Opgave om logging af transaktioner i en bank med filialer | I forlængelse af ovenstående eksempel, hvor to filialer i en bank opererer samtidigt på fælles konti, bedes I indføre
en BankLogging klasse, som skal holde styr på følgende:
Vi kan bl.a. bruge denne log til at finde ud af i hvilken rækkefølge operationerne udføres fra de to samtidigt udførende filialer i eksemplet ovenfor. Hvad angår tidsbehandling henvises til metoden getTime mv. i klassen java.util.Calendar. |
Detaljer om synkroniserede metoder Slide Note Contents Index References | Vi giver her en række detaljer om synkroniserede metoder i Java |
Årsagen til, at kontruktorer ikke skal synkroniseres er, at objektet skal skabes inden der er behov for konflikter ved samtidig tilgang. Og vi er netop igang med at skabe (initialisere) objektet i en konstruktor |
Trådsikre klasser Slide Note Contents Index References | Man taler ofte om trådsikre klasse biblioteker i forbindelse med programmeringssprog, som understøtter flere samtidige programforløb. Det engelske ord er 'thread safeness' |
The concept trådsikker klasse: En klasse siges at være trådsikker hvis den er beskyttet mod multipel og samtidig tilgang fra flere tråde | Vi siger at en klasse er trådsikker (på engelsk 'thread safe') hvis den er beskyttet mod multipel og samtidig tilgang fra flere tråde. Beskyttelsen består i at hindre flere tråde i samtidig tilgang, hvor der opstår konflikt mellem to opdateringer, mellem en opdatering og en aflæsning, eller lignende |
|
Java's synchronized kommando Slide Note Contents Index References | Java's synchronized kommando låser et objekt uden brug af låsemekanismen i synkroniserede metoder |
|
Syntax: Syntaks for en synkroniseret blok. Udtrykket evalueres. Resultatet er et objekt som låses for samtidig adgang fra andre tråde under udførelse af blokken |
|
Generelt forekommer det, at anvendelse af en synchronized kommando er en dårlig løsning i forhold til at bruge synchronized metoder i en klasse. Eksemplet på næste slide illustrerer denne påstand Ved at introducere det låse objekt, hvorpå der synkroniseres med en synchronized kommando, kan man sikre udelelig adgang til en ressource fra blokke i flere tråde. Hver tråd skal blot bruge en synchronized kommando på objektet. Synkroniseringskommandoen og objektet tjener som en indgang til ressourcen, der samtidig låser for andre trådes tilgang |
|
Synkroniserede konti via synchronized kommandoer Slide Note Contents Index References | Vi gentager ovenstående synkroniseret konto eksempel, nu blot vist med synchronized kommandoer i stedet for synkroniserede metoder |
|
Program: Java klassen Konto.
Denne klasse svarer til vores oprindelige Konto klasse, som vi udviklede i en af de første lektioner
i dette kursus.
|
|
Program: En bank som opstarter to filialer der gennemfører ens transaktioner.
Transaktionerne her er lidt anderledes end i det tilsvarende eksempel ovenfor. Stadig udfører
hver 'filial' ens transaktioner (hvilket naturligvis er lidt sært, men let for programmøren af denne klasse).
Vi ser et væld at synkroniseringer på konto1 hhv. konto2 for at sikre mod samtidige opdateringer i de to
filialer
|
|
Program: Det samlede program med Konto og 'filial opstart'.
| ![]() |
|
Monitorer Slide Note Contents Index References | Hvis man interesserer sig for samtidige programforløb er monitorer er et vigtigt begreb. Selv om vi reelt allerede har dækket essensen af begrebet gør vi det her monitorbegrebet eksplicit |
|
The concept monitor: En klasse med synkroniserede metoder er også kendt som en monitor. Metoderne udgør kritiske regioner. Internt er der tilknyttet en kø af ventende tråde til et monitor objekt | Monitorer blev oprindelig beskrevet af Hoare (som hører til pionererne i faget). Vi definerer her en monitor som en en klasse med synkroniserede metoder. Dette er ikke den oprindelige definition (alene af den grund, at monitorer blev beskrevet i et ikke-objekt-orienteret programmeringssprog). Men det ville være bagvendt her at indføre en mere basal definition, der i et og alt leder frem til det sammen som den her givne. |
Java's beskrivelse af monitorer kan bl.a. findes i klassen Object, ved metoderne notify og wait, jf. nedenstående reference til API dokumentationen i Java Development Kit |
|
Synkronisering: For lidt og for meget Slide Note Contents Index References | Vi dvæler her ved retningslinier for hvor store programdele der bør findes i synkroniserede metoder |
|
Når vi siger 'for meget synkronisering' tænker vi på situationen hvor to eller flere tråde udføre store dele af tråden inden for en synkroniseret metode. Derved er det objekt, hvor metoden er placeret, låst. Hermed kan ingen andre udføre metoder på objektet |
|
Eksempel på et program med synkronisering: Producent og Forbruger Slide Note Contents Index References | Producent Forbruger eksemplet er klassisk hvad angår illustrationen af to tråde eller processer, som skal samarbejde på en synkroniseret måde. Eksemplet er også meget relevant i den praktiske verden |
|
Eksempel. 'Producent Forbruger' er et klassisk eksempel på et samarbejde mellem to processer. Producenten fremstiller objekter, som sendes til forbrugeren. Forbrugeren modtager objekter, og benytter dem til et bestemt formål |
Program: Producent klassen.
Denne klasse laver objekter (her blot tal) som skal afleveres til forbruger objektet.
Når denne anstrengelse er overstået er der behov for at hvile ud - samle kræfter -
så næste ting kan produceres (sleep).
|
|
Program: Consumer klassen.
Denne klasse forbruger de objekter, der modtages fra producent klassen. Forbruget består her i blot at udskrive objekterne (tal)
|
|
Program: En simpel udgave af cubbyhole klassen, som ikke virker.
Denne udgave er simplificeret helt ind til benet.
Vi medtager alene denne simple udgave af postkassen for at motivere os selv til at
lave en, som faktisk virker.
|
|
Program: Klasse med en statisk main metode, der laver CubbyHole, og igangsætter Producer og Consumer.
|
|
Program: Output fra kørsel af programmet ovenfor, som illustrerer problemet.
Vi ser at tallet 0 bliver produceret og forbrugt korrekt.
Men 2 bliver produceret for hurtigt, og således at 1 aldrig bliver
forbrugt. Det samme gør sig gældende for 3, osv.
|
|
Program: En forbedret CubbyHole klassen.
Der kan i dette eksempel kun opbevares ét objekt i postkassen.
Det er vigtigt at forstå, at vi ikke kender til rækkefølgen af kald til put og get.
Hvis put kaldes først (af producenten) skal vi sikre os, at vi ikke overskriver et allerede eksisterende objekt (contents).
Den boolske variable available er falsk hvis der er plads til et objekt i Cubbyhole.
Hvis get kaldes først (af forbrugeren) bliver vi nødt til at vente på at forbrugeren skal putte noget i postkassen.
While løkken i get kan forstås som 'afvent at der noget i postboksen' (altså available er true).
While løkken i put kan forstås som 'afvent at postboksen er tom' (altså available er false).
Bemærk brugen af while (idiomatisk anvendelse, jf. efterfølgende slide).
|
|
Program: Output fra kørsel af programmet ovenfor.
|
|
Klasserne vist ovenfor stammer direkte fra et eksempel i The Java Tutorial, jf hosstående henvisning |
|
Wait og notify Slide Note Contents Index References | Wait og notify er primitiverne til som tillader os at programmere synkroniserede tråde. Vi ser her nærmere på disse primitiver |
|
|
|
Wait og Notify idioms Slide Note Contents Index References | Der kan anvises nogle typiske mønstre og regler for anvendelse af wait og notify til synkronisering af samarbejdende tråde. Vi ser her nærmere på disse 'idioms' |
|
Program: Typiske mønstre for anvendelse af wait of notify metoderne i en klasse, der virker som en monitor.
|
|
|
Eksempel på et program med synkronisering: Synkroniseret Sangkor Slide Note Contents Index References | Vi vender nu tilbage til sangkor eksemplet. I vores første møde med sangkoret blev de enkelte strofer sunget hulter til bulter. I det næste eksempel justerede vi på prioriteterne, således at den ene sang blev sunget før den anden. Dette var dog ikke nogen garanteret egenskab, men blot et udfald af skeduleringsalgoritmens virkemåde. Nu ønsker vi at lave en version af sangkoret, som skiftevis synger et vers af den ene og den anden sang. (Stadig gentager vi blot samme vers flere gange). Versionen, som vi udvikler her skal garantere, at den skiftevise rækkefølge mellem versene opnås |
Eksempel. I SangKor eksemplet ønsker vi nu en synkroniseret opførsel, således at der skiftevis lyder et vers fra hver sang |
Program: Klassen Skjald som på disciplineret vis indtager og frigiver scenen.
|
|
Program: Klassen Dirigent som synkroniserer to Skjalde.
Betingelsen i while løkken siger at scenen enten ikke er ledig eller at det er den anden Skjald's tur.
Under disse omstændigheder skal der ventes
|
|
Program: Klassen Sangkor3, som starter to Skjalde og en Dirigent.
|
|
Program: Output fra ovenstående program.
Som det var ønsket ser vi at vers fra de to sange synges på skift.
|
|
Program: Det samlede program med klasserne Sangkor3, Skjald og Dirigent.
| ![]() |
Exercise 14.2. Opgave om synkroniserede skjalde | I forlængelse af ovenstående eksempel kan man observere, at hvis den ene sang synges (f.eks.) 2 gange, og den anden
sang 4 gange kører det samlede Java program aldrig til ende.
|
Skedulerende og synkroniserende metoder: oversigt Slide Note Contents Index References | Der er andre skedulerende og synkroniserende primitiver end wait og notify. Vi ser her på et bredt udvalg, som næsten kommer omkring alle relevante metoder til formålene |
|
|
Trådgrupper |
Trådgrupper Slide Note Contents Index References | I Java kan tråde organiseres i grupper (thread groups), som selv kan indeholde trådgrupper. På denne måde kan tråde i Java struktureres i et hierarki, hvis niveauer på flere måder kan organiseres under ét. Trådgrupper er først og fremmest relevante hvis man har mange tråde i et Java program |
|
The concept trådgruppe: En trådgruppe er en mængde af tråde, som rekursivt kan indeholde andre trådgrupper | En trådgruppe definerer et træ hvor bladene er tråde. De indre knuder er trådgrupper |
|
|
Chapter 14: Samtidighed i Java
Course home Author home About producing this web Previous lecture (top) Next lecture (top) Previous lecture (bund) Next lecture (bund)
Generated: March 31, 2008, 12:09:55