Threadlocal storage November 18, 2008 07:19 about 1 month ago
Jeg er midt i en debugsession i en Java applikation som nægter at virke. Maskinen er en 64bit server med 16 fysiske CPU’er. Efter et par timer går det op for mig at Java’s threadlocal er årsagen. Applikationen køre fint på en dual core maskine hvor CPU’er har fælles hukommelse men kan ikke køre på en maskine hvor alle CPU’er har hver deres hukommelse. Jeg går ud fra at det underliggende operativsystem skifter CPU hvorefter min Java proces mister sin kontekst.
ThreadLocal er en slags global variable allokeret til en tråd på en måde så den er synlig for alle objekter der eksekveres i denne i denne tråd. Denne metode benyttes ofte til at binde fx bruger-id eller transaction-id til en tråd og i meget sjældent tilfælde en database forbindelse. I dette tilfælde er det det sidste.
Hvis man kigger ind i threadlocal klassen finder man en nøgle med værdien 0×61c88647 og alle nye objekter af denne type tildeles værdien hash_increment plus værdien 0×61c88647. Altså, værdien 0×61c88647 er jo et tal i 16tals formatet og hvis man omsætter det til et decimaltal får man enten 2654435769 eller -1640531527. Det er en 32-bit signed version af den usignerede 2654435769 og er den faktiske værdi som Java bruger til at beregne hash.
Tallet repræsenterer ”The Golden Section” eller den gyldne ratio(sqrt (5) -1) gange to til potensen af 31. Check Dr Ron Knott algoritmer og Donald Knuth, de bøger hører hjemme hos en enhver seriøs programmør. Måske også Merck Manual ”adorns your health practitioner’s”.
Koden for threadlocal en smule kompliceret og jeg må ty til Dr Heinz for en forklaring. Tanken bag tidligere versioner af Java threadlocals var at dele state mellem flere tråde hvilket gør dem praktisk talt ubrugelige for multi-core-applikationer. I Java 1.4, blev et nyt design introduceret hvori threadlocals blev opbevaret direkte i den eksekverede tråd. Når man kalder metoden get() sker det på tråden og der returneres en instans af threadlocalmap som er en slags indre klasse af threadlocal. I teorien skulle en udgående tråd fjerne sine threadlocal værdier lige før garbage collection kaldes med exit() metoden. Hvis vi derimod glemme at fjerne en threadlocal er den stadig berettiget til automatisk garbage collection. Det skyldes en weak references intern i threadlocal til threadlocalmap der har normalt stærke henvisninger til værdier.
Et threadlocal objekt bliver garbage collected når den eksekverede tråd dør. Undtagen hvis tråden er blevet trukket ud af en objekt pool som fx i nogle applikationsserverer, i disse tilfælde bliver værdierne aldrig garbage collected.
Når et threadlocal objekt bliver garbage collected bliver den svage reference threadlocalmap ryddet. Hvornår bliver det så virkeligt slettet fra threadlocalmap. Når vi kalder get() metoden på en indgang i java.util.WeakHashMap fjernes alle gamle indgange. Hvis vi kalder get() på threadlocalmap går det hurtigere men kan give forældet poster og dermed memory leaks.
Når man kalder ThreadLocal set() kan det falde ind under disse kategorier:
- hvis værdi ikke findes indsættes den.
- hvis vi finder vores nøgle, byttes den med ny værdi.
- hvis der ikke er plads til flere værdier. Denne fase bruger en O(log2n) algoritmen i første omgang.
- hvis en nøglen er fjernet så er posten slettet.
Hvis du ønsker flere oplysninger om, hvordan det virker, kan du få nogle ideer fra Knuth 6.4 AlgoritmeR.
Best Practices hvis du absolut vil bruge threadlocals skal du sørge for at fjerne de indsatte værdier når du er færdig med dem, og helst inden du afleverer din tråd til en tråd pool. Den bedste måde at gøre dette på er at bruge metoden remove() i stedet for set(null) som vil medføre at weak reference mappe bliver fjernes straks sammen med værdien.
By Frank Vilhelmsen - 2 tags: java multicore - Add comment


