Det finns olika nivåer på "thread-safety". Java-gurun Joshua Bloch definierar fem st. nivåer (i bokens 'Item 52', samtidigt som han poängterar att det inte finns några "widely accpeted conventions in this area" avseende terminologin/definitionerna) i sin prisbelönta bok "Effective Java Programming Language Guide ", varav en nivå är "immutable" vilket innebär att ett objekts tillstånd inte kan förändras och att det därför är riskfritt att dela det mellan olika trådar.
Angående Java vs C#.NET så är väldigt mycket i denna bok även applicerbart för C#, bl.a. koncepten om olika nivåer på thread-safety.
Ett relevant citat från Bloch:
"Moreover, the claim that the presence of the synchronized keyword is sufficient to document thread safety embodies the common misconception that thread safety is an all-or-nothing property. In fact, there are many levels of thread safety that a class can support."
(eventuellt nödvändig förklarande information till .NET-programmerare: java 'synchronized' = C# 'lock')
Vad är man då egentligen ute efter när man säger att man eftersträvar trådsäkerhet ?
Jo, det bör rimligtvis vara att man vill undvika de potentiella problem som kan uppstå i en flertrådad applikation.
Om man tittar på klassen 'Car' så kan man konstatera att den är "mutable" (går att förändra) eftersom tillståndet kan förändras via set-properties, och om instanser delas mellan olika trådar så kan faktiskt problem uppstå, beroende på hur klienterna använder klassen.
Antag t.ex. att huvudtråden i applikationen hämtar en lista med alla bilar så här:
'List<Car> cars = Car.getAll();'
och att den sedan skapar två nya trådar och till båda dessa skickar med en referens till 'cars' som de två trådarna lagrar i egna fält/medlemsvariabler.
Antag sedan t.ex. att en metod i en av trådarna innehåller följande kod:
(rad 1) List<string> attributes = this.cars[0].Attributes;
(rad 2) attributes.Add("abc");
(rad 3) string s = attributes[attributes.Count-1];
Efter exekveringen av rad 3 är det inte helt säkert att variabel s kommer att innehålla strängen "abc" eftersom det kan vara på det viset att mellan exkeveringen av rad 2 och rad 3 så har den andra tråden via sin referens till cars anropat t.ex. 'car [0].Attributes.Add("xyz")' och lagt till ytterligare ett attribut som den första tråden inte hade räknat med.
Gladh skrev:
Addeladde skrev:
Är detta trådsäkert?
Som jag kan se det så skulle detta vara trådsäkert eftersom private List<string> attributes inte kommer att delas mellan några trådar.
Du skapar en klass car i din static-metod, det betyder att denna klassen kommer få en egen minnesrymd för varje tråd som accessar metoden. Så jag kan inte se att du skulle få några problem med trådar i denna kod.
Jo, visserligen skapas både 'List<Car>' och 'Car' instanserna som lokala variabler i static-metoden 'Car.getAll()', men den anropande koden kan alltså spara en referens (och dela den referensen med andra trådar !) till det returnerade 'List<Car>'-objektet, och eftersom såväl List-objektet som de ingående Car-instanserna (och 'Car.Attributes'-propertyn) är "mutable" så kan man alltså få problem i en flertrådad applikation.
Om man själv implementerar klientkoden så kan man visserligen undvika att lagra och dela en referens till det returnerade objektet genom att varje tråd får själv anropa 'Car.getAll()' för att erhålla nya egna instanser till collectionen och car-objekten, så att ingen annan tråd samtidigt modifierar samma car-instanser.
Å andra sidan (i framtiden när applikationen har vuxit) kanske någon programmerare kan komma att få för sig att vilja "optimera" getAll-metoden och cacha den (d.v.s. lagra den i en referens istället för att skapa en collection vid varje anrop) med lite lazy loading så att koden som tidigare fungerade p.g.a. att klienten förväntade sig egna instanser, kan sluta att fungera tillfredsställande p.g.a. att flera trådar nu plötsligt modifierar samma instanser. Då man använder förändringsbara (mutable) objekt så kan alltså problem alltid tänkas uppstå om flera trådar delar referens till samma objekt, och den ena tråden förväntar sig att de modifieringar av tillstånd som den själv triggar kommer att bibehållas mellan metod-anropen.
Ett annat litet exempel, om en referens till ett car-objekt delas mellan flera trådar, så kanske den ena tråden vill sätta en färg innan en annan metod anropas:
car.Color = color;
car.MetodSomAnvanderColor();
men mellan dessa anrop så kan en annan tråd också ha ändrat Color på samma car-instans.