5000å10å¼ å¾è®²æ¸ æ¥æ¬å°åéThreadLocal
æ¯ Java 䏿ä¾çä¸ç§ç¨äºå®ç°çº¿ç¨å±é¨åéçå·¥å ·ç±»ãå®å 许æ¯ä¸ªçº¿ç¨é½æ¥æèªå·±çç¬ç«å¯æ¬ï¼ä»èå®ç°çº¿ç¨é离ï¼ç¨äºè§£å³å¤çº¿ç¨ä¸å ±äº«å¯¹è±¡ç线ç¨å®å ¨é®é¢ã
éå¸¸ï¼æä»¬ä¼ä½¿ç¨ synchronzed å ³é®å æè lock æ¥æ§å¶çº¿ç¨å¯¹ä¸´çåºèµæºçåæ¥é¡ºåºï¼ä½è¿ç§å éçæ¹å¼ä¼è®©æªè·åå°éç线ç¨è¿è¡é»å¡ï¼å¾æ¾ç¶ï¼è¿ç§æ¹å¼çæ¶é´æçä¸ä¼ç¹å«é«ã
线ç¨å®å ¨é®é¢çæ ¸å¿å¨äºå¤ä¸ªçº¿ç¨ä¼å¯¹åä¸ä¸ªä¸´çåºçå ±äº«èµæºè¿è¡è®¿é®ï¼é£å¦ææ¯ä¸ªçº¿ç¨é½æ¥æèªå·±çâå ±äº«èµæºâï¼åç¨åçï¼äºä¸å½±åï¼è¿æ ·å°±ä¸ä¼åºç°çº¿ç¨å®å ¨çé®é¢äºï¼å¯¹å§ï¼
äºå®ä¸ï¼è¿å°±æ¯ä¸ç§âç©ºé´æ¢æ¶é´âçææ³ï¼æ¯ä¸ªçº¿ç¨æ¥æèªå·±çâå ±äº«èµæºâï¼è½ç¶å åå ç¨å大äºï¼ä½ç±äºä¸éè¦åæ¥ï¼ä¹å°±åå°äºçº¿ç¨å¯è½åå¨çé»å¡é®é¢ï¼ä»èæé«æ¶é´ä¸çæçã
ä¸è¿ï¼ThreadLocal å¹¶ä¸å¨ java.util.concurrent å¹¶åå ä¸ï¼èæ¯å¨ java.lang å ä¸ï¼ä½ææ´å¾åäºæå®å½ä½æ¯ä¸ç§å¹¶å容å¨ã
顾åæä¹ï¼ThreadLocal å°±æ¯çº¿ç¨çâæ¬å°åéâï¼å³æ¯ä¸ªçº¿ç¨é½æ¥æè¯¥åéçä¸ä¸ªå¯æ¬ï¼è¾¾å°äººæä¸ä»½çç®çï¼è¿æ ·å°±å¯ä»¥é¿å å ±äº«èµæºçç«äºã
ThreadLocal çæºç åæ
set æ¹æ³
set æ¹æ³ç¨äºè®¾ç½®å½å线ç¨ä¸ ThreadLocal çåéå¼ï¼è¯¥æ¹æ³çæºç å¦ä¸ï¼
public void set(T value) {
//1. è·åå½å线ç¨å®ä¾å¯¹è±¡
Thread t = Thread.currentThread();
//2. éè¿å½å线ç¨å®ä¾è·åå°ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
//3. 妿Mapä¸ä¸ºnull,å以å½åThreadLocalå®ä¾ä¸ºkey,å¼ä¸ºvalueè¿è¡åå
¥
map.set(this, value);
else
//4.map为null,åæ°å»ºThreadLocalMapå¹¶åå
¥value
createMap(t, value);
}- éè¿
Thread.currentThread()æ¹æ³è·åå½åè°ç¨æ¤æ¹æ³ç线ç¨å®ä¾ã - æ¯ä¸ªçº¿ç¨é½æèªå·±ç ThreadLocalMapï¼è¿ä¸ªæ å°è¡¨åå¨äºçº¿ç¨çå±é¨åéï¼å ¶ä¸é®æ¯ ThreadLocal 对象ï¼å¼ä¸ºç¹å®äºçº¿ç¨ç对象ã
- 妿 Map ä¸ä¸º nullï¼å以å½å ThreadLocal å®ä¾ä¸º keyï¼å¼ä¸º value è¿è¡åå ¥ï¼å¦æ map 为 nullï¼åæ°å»º ThreadLocalMap å¹¶åå ¥ valueã
éè¿æºç æä»¬ç¥éï¼value æ¯åæ¾å¨ ThreadLocalMap éçãæ¥çä¸ ThreadLocalMap æ¯ä»ä¹ï¼å æä¸ªç®åç认è¯ï¼åé¢ä¼ç»è®²ã
ThreadLocalMap æ¯ææ ·æ¥çå¢ï¼éè¿getMap(t)ï¼
ThreadLocalMap getMap(Thread t) {
return t.ThreadLocals;
}è¯¥æ¹æ³ç´æ¥è¿åå½å线ç¨å¯¹è±¡ t çä¸ä¸ªæååé ThreadLocalsï¼
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap ThreadLocals = null;忥ç set æ¹æ³ï¼å½ map 为 null çæ¶åä¼éè¿createMap(tï¼value)æ¹æ³ new åºæ¥ä¸ä¸ªï¼
void createMap(Thread t, T firstValue) {
t.ThreadLocals = new ThreadLocalMap(this, firstValue);
}è¯¥æ¹æ³ new äºä¸ä¸ª ThreadLocalMap å®ä¾å¯¹è±¡ï¼ç¶å以å½å ThreadLocal å®ä¾ä½ä¸º keyï¼å¼ä¸º value åæ¾å° ThreadLocalMap ä¸ï¼ç¶åå°å½å线ç¨å¯¹è±¡ç ThreadLocals èµå¼ä¸º ThreadLocalMap 对象ã
set æ¹æ³çéè¦æ§å¨äºå®ç¡®ä¿äºæ¯ä¸ªçº¿ç¨é½æèªå·±çåé坿¬ãç±äºè¿äºå鿝åå¨å¨ä¸çº¿ç¨å ³èçæ å°è¡¨ä¸çï¼æä»¥ä¸åç线ç¨ä¹é´çè¿äºåéäºä¸å½±åã
get æ¹æ³
get æ¹æ³ç¨äºè·åå½å线ç¨ä¸ ThreadLocal çåéå¼ï¼åæ ·çè¿æ¯æ¥çæºç ï¼
public T get() {
//1. è·åå½å线ç¨çå®ä¾å¯¹è±¡
Thread t = Thread.currentThread();
//2. è·åå½å线ç¨çThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//3. è·åmapä¸å½åThreadLocalå®ä¾ä¸ºkeyçå¼çentry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//4. å½åentitiyä¸ä¸ºnullçè¯ï¼å°±è¿åç¸åºçå¼value
T result = (T)e.value;
return result;
}
}
//5. è¥map为nullæè
entry为nullçè¯éè¿è¯¥æ¹æ³åå§åï¼å¹¶è¿åè¯¥æ¹æ³è¿åçvalue
return setInitialValue();
}代ç é»è¾è¯·ç注éï¼æä»¬æ¥çä¸ setInitialValue 主è¦åäºäºä»ä¹äºæ ï¼
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}è¯¥æ¹æ³çé»è¾å set æ¹æ³å ä¹ä¸æ ·ï¼ä¸»è¦æ¥çä¸ initialValue æ¹æ³:
protected T initialValue() {
return null;
}è¿ä¸ªæ¹æ³æ¯éè¿ protected 修饰çï¼ä¹å°±æå³ç ThreadLocal çåç±»å¯ä»¥éåè¯¥æ¹æ³ç»ä¸ä¸ªåéçåå§å¼ã
è¿éæ¯ initialValue æ¹æ³çå ¸åç¨æ³ï¼
private static ThreadLocal<Integer> myThreadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0; // åå§å¼è®¾ç½®ä¸º0
}
};æ¤ä»£ç 段å建äºä¸ä¸ªæ°ç ThreadLocal<Integer> 对象ï¼å
¶åå§å¼ä¸º 0ãä»»ä½å°è¯é¦æ¬¡è®¿é®æ¤ ThreadLocal åéç线ç¨é½ä¼çå°å¼ 0ã
æ´ä¸ª setInitialValue æ¹æ³çç®çæ¯ç¡®ä¿æ¯ä¸ªçº¿ç¨å¨ç¬¬ä¸æ¬¡å°è¯è®¿é®å ¶ ThreadLocal å鿶齿ä¸ä¸ªåéçå¼ãè¿ç§âææ°âåå§åçæ¹æ³ç¡®ä¿äºä» å¨å®é éè¦ç¹å®äºçº¿ç¨ç弿¶æå建è¿äºå¼ã
remove æ¹æ³
public void remove() {
//1. è·åå½å线ç¨çThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//2. ä»mapä¸å é¤ä»¥å½åThreadLocalå®ä¾ä¸ºkeyçé®å¼å¯¹
m.remove(this);
}remove æ¹æ³çä½ç¨æ¯ä»å½å线ç¨ç ThreadLocalMap ä¸å é¤ä¸å½å ThreadLocal å®ä¾å ³èçæ¡ç®ãè¿ä¸ªæ¹æ³å¨éæ¾çº¿ç¨å±é¨åéçèµæºæé置线ç¨å±é¨åéç弿¶ç¹å«æç¨ã
以䏿¯ä½¿ç¨ remove æ¹æ³ç示ä¾ä»£ç ï¼
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Initial Value");
Thread thread = new Thread(() -> {
System.out.println(threadLocal.get()); // è¾åº "Initial Value"
threadLocal.set("Updated Value");
System.out.println(threadLocal.get()); // è¾åº "Updated Value"
threadLocal.remove();
System.out.println(threadLocal.get()); // è¾åº "Initial Value"
});
thread.start();è¾åºç»æï¼
Initial Value
Updated Value
Initial ValueThreadLocalMap çæºç åæ
ThreadLocalMap æ¯ ThreadLocal ç±»çéæå é¨ç±»ï¼å®æ¯ä¸ä¸ªå®å¶çåå¸è¡¨ï¼ä¸é¨ç¨äºä¿åæ¯ä¸ªçº¿ç¨ä¸ç线ç¨å±é¨åéã
static class ThreadLocalMap {}å大夿°å®¹å¨ä¸æ ·ï¼ThreadLocalMap å é¨ç»´æ¤äºä¸ä¸ª Entry ç±»åçæ°ç» ç±»åçæ°ç» tableï¼é¿åº¦ä¸º 2 ç广¬¡æ¹ãã
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;æ¥çä¸ Entry æ¯ä»ä¹ï¼
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}Entry ç»§æ¿äºå¼±å¼ç¨ WeakReference<ThreadLocal<?>>ï¼å®ç value åæ®µç¨äºåå¨ä¸ç¹å® ThreadLocal 对象å
³èçå¼ã使ç¨å¼±å¼ç¨ä½ä¸ºé®å
许å徿¶éå¨å¨ä¸åéè¦çæ
åµä¸åæ¶ ThreadLocal å®ä¾ã
è¿éæä»¬å¯ä»¥ç¨ä¸å¼ 徿¥çè§£ä¸ ThreadãThreadLocalãThreadLocalMapãEntry ä¹é´çå ³ç³»ï¼

ä¸å¾ä¸çå®çº¿è¡¨ç¤ºå¼ºå¼ç¨ï¼è线表示弱å¼ç¨ãæ¯ä¸ªçº¿ç¨é½å¯ä»¥éè¿ ThreadLocals è·åå° ThreadLocalMapï¼è ThreadLocalMap å®é ä¸å°±æ¯ä¸ä¸ªä»¥ ThreadLocal å®ä¾ä¸º keyï¼ä»»æå¯¹è±¡ä¸º value ç Entry æ°ç»ã
å½æä»¬ä¸º ThreadLocal åéèµå¼æ¶ï¼å®é ä¸å°±æ¯ä»¥å½å ThreadLocal å®ä¾ä¸º keyï¼å¼ä¸º Entry å¾è¿ä¸ª ThreadLocalMap ä¸åæ¾ã
注æï¼Entry ç key 为弱å¼ç¨ï¼æå³çå½ ThreadLocal å¤é¨å¼ºå¼ç¨è¢«ç½®ä¸º nullï¼ThreadLocalInstance=nullï¼æ¶ï¼æ ¹æ®å¯è¾¾æ§åæï¼ThreadLocal å®ä¾æ¤æ¶æ²¡æä»»ä½ä¸æ¡é¾è·¯å¼ç¨å®ï¼æä»¥ç³»ç» GC çæ¶å ThreadLocal ä¼è¢«åæ¶ã
è¿æ ·ä¸æ¥ï¼ThreadLocalMap å°±ä¼åºç° key 为 null ç Entryï¼ä¹å°±æ²¡åæ³è®¿é®è¿äº key 对åºç valueï¼å¦æçº¿ç¨è¿è¿ä¸ç»æçè¯ï¼è¿äº key 为 null ç value å°±ä¼ä¸ç´åå¨ä¸æ¡å¼ºå¼ç¨é¾ï¼Thread Ref -> Thread -> ThreaLocalMap -> Entry -> valueï¼æ æ³åæ¶å°±ä¼é æå åæ³æ¼ã
å½ç¶ï¼å¦æ thread è¿è¡ç»æï¼ThreadLocalãThreadLocalMapãEntry 没æå¼ç¨é¾å¯è¾¾ï¼å¨åå¾åæ¶æ¶é½ä¼è¢«ç³»ç»åæ¶ãä½å®é å¼åä¸ï¼çº¿ç¨ä¸ºäºå¤ç¨æ¯ä¸ä¼ä¸»å¨ç»æçï¼æ¯å¦è¯´æ°æ®åºè¿æ¥æ± ï¼è¿å¤§ççº¿ç¨æ± å¯è½ä¼å¢å å åæ³æ¼çé£é©ï¼å æ¤åçé ç½®çº¿ç¨æ± ç大å°å线ç¨çåæ´»æ¶é´æå©äºåè½»è¿ä¸ªé®é¢ã
为äºé¿å è¿ä¸ªé®é¢ï¼å¨æ¯æ¬¡ä½¿ç¨å® ThreadLocal ä¹åï¼æå¥½æç¡®è°ç¨ ThreadLocal ç remove æ¹æ³æ¥å é¤ä¸å½å线ç¨å ³èçå¼ãè¿æ ·å¯ä»¥ç¡®ä¿çº¿ç¨åæ¬¡ä½¿ç¨æ¶ä¸ä¼å卿§çãä¸åéè¦çå¼ã
ä¸ ConcurrentHashMapãHashMap ç容å¨ä¸æ ·ï¼ThreadLocalMap 乿¯éè¿åå¸è¡¨å®ç°çã
åå¸è¡¨
åå¸è¡¨æ¯åºäºæ°ç»çï¼æ¯ä¸ªæ°ç»å ç´ è¢«ç§°ä¸ºä¸ä¸ªâæ¡¶âï¼Bucketï¼ï¼æ¡¶ä¸åå¨äºé®å¼å¯¹ï¼Key-Value Pairï¼ï¼é®æ¯éè¿åå¸å½æ°çæçï¼çæ³çåå¸å½æ°å¯ä»¥åååå¸é®ï¼ä»èæå¤§é度å°åå°å²çªã

çæ³çåå¸å½æ°å¯ä»¥åååå¸é®ï¼ä»èæå¤§é度å°åå°å²çªãå½ä¸¤ä¸ªæå¤ä¸ªé®çåå¸å¼ç¸åï¼å³æ å°å°åä¸ä¸ªæ¡¶ï¼æ¶ï¼ç§°ä¹ä¸ºåå¸å²çªã常è§çè§£å³çç¥ææé¾æ³å弿¾å°åæ³ã
æé¾æ³
å¨è®² HashMap çæ¶åï¼æä»¬è¯¦ç»è®²è¿æé¾æ³ï¼ç¸ä¿¡å¤§å®¶é½è¿æå°è±¡ï¼æä»¬è¿éç®åå顾ä¸ä¸ï¼å½æé¡¹å ³é®åéè¿åå¸åè½å°åå¸è¡¨ä¸çæä¸ªä½ç½®ï¼æè¯¥æ¡æ°æ®æ·»å å°é¾è¡¨ä¸ï¼å ¶ä»åæ ·æ å°å°è¿ä¸ªä½ç½®çæ°æ®é¡¹ä¹åªéè¦æ·»å å°é¾è¡¨ä¸ãä¸é¢æ¯ç¤ºæå¾ï¼

弿¾å°åæ³
弿¾å°åæ³ä¸ï¼è¥æ°æ®ä¸è½ç´æ¥åæ¾å¨åå¸å½æ°è®¡ç®åºæ¥çæ°ç»ä¸æ æ¶ï¼å°±éè¦å¯»æ¾å ¶ä»ä½ç½®æ¥åæ¾ãå¨å¼æ¾å°åæ³ä¸æä¸ç§æ¹å¼æ¥å¯»æ¾å ¶ä»çä½ç½®ï¼å嫿¯ãçº¿æ§æ¢æµãããäºæ¬¡æ¢æµãããåå叿³ãã
01ãçº¿æ§æ¢æµï¼å½åå¸å½æ°è®¡ç®åºæ¥çæ°ç»ä¸æ å·²ç»è¢«å ç¨æ¶ï¼å°±é¡ºåºå¾åæ¥æ¾ï¼ç´å°æ¾å°ä¸ä¸ªç©ºé²çä½ç½®ã
ä¾å¦æä»¬å°æ°88ç»è¿åå¸å½æ°åå¾å°çæ°ç»ä¸æ æ¯16ï¼ä½æ¯å¨æ°ç»ä¸æ 为16çå°æ¹å·²ç»åå¨å ç´ ï¼é£ä¹å°±æ¾17ï¼17è¿åå¨å ç´ å°±æ¾18ï¼ä¸ç´å¾ä¸æ¾ï¼ç´å°æ¾å°ç©ºç½å°æ¹åæ¾å ç´ ãæä»¬æ¥çä¸é¢è¿å¼ å¾ï¼

æä»¬ååå¸è¡¨ä¸æ·»å ä¸ä¸ªå ç´ é±å¤å¤ï¼é±å¤å¤ç»è¿åå¸å½æ°åå¾å°çæ°ç»ä¸æ 为0ï¼ä½æ¯å¨0çä½ç½®å·²ç»æå¼ ä¸äºï¼æä»¥ä¸æ å¾åç§»ï¼ç´å°ä¸æ 4æä¸ºç©ºï¼æä»¥å°±å°å ç´ é±å¤å¤æ·»å å°æ°ç»ä¸æ 为4çå°æ¹ã
02ãäºæ¬¡æ¢æµï¼å½åå¸å½æ°è®¡ç®åºæ¥çæ°ç»ä¸æ å·²ç»è¢«å ç¨æ¶ï¼å°±é¡ºåºå¾åæ¥æ¾ï¼ç´å°æ¾å°ä¸ä¸ªç©ºé²çä½ç½®ãä¸åçæ¯ï¼äºæ¬¡æ¢æµæ¯æç §æç§è§å¾æ¥æ¾ï¼è䏿¯é¡ºåºæ¥æ¾ï¼æ¯å¦è¯´æ¯æ¬¡æ¥æ¾çæ¥é¿æ¯ 1ï¼2ï¼4ï¼8ï¼16â¦â¦
å¨çº¿æ§æ¢æµåå¸è¡¨ä¸ï¼æ°æ®ä¼åçèéï¼ä¸æ¦èéå½¢æï¼å®å°±ä¼åçè¶æ¥è¶å¤§ï¼é£äºåå¸å½æ°åè½å¨èéèå´å çæ°æ®é¡¹ï¼é½éè¦ä¸æ¥ä¸æ¥å¾åç§»å¨ï¼å¹¶ä¸æå ¥å°èéçåé¢ï¼å æ¤èéåçè¶å¤§ï¼èéå¢é¿çè¶å¿«ãè¿ä¸ªå°±åæä»¬å¨éè¶ å¸ä¸æ ·ï¼å½æä¸ªå°æ¹äººå¾å¤æ¶ï¼äººåªä¼è¶æ¥è¶å¤ï¼å¤§å®¶é½åªæ¯æ³ç¥éè¿éå¨å¹²ä»ä¹ã
äºæ¬¡æ¢æµæ¯é²æ¢èé产ççä¸ç§å°è¯ï¼ææ³æ¯æ¢æµç¸éè¾è¿çåå ï¼è䏿¯ååå§ä½ç½®ç¸é»çåå ãå¨çº¿æ§æ¢æµä¸ï¼å¦æåå¸å½æ°å¾å°çåå§ä¸æ æ¯xï¼çº¿æ§æ¢æµå°±æ¯x+1,x+2,x+3......ï¼ä»¥æ¤ç±»æ¨ï¼èå¨äºæ¬¡æ¢æµä¸ï¼æ¢æµè¿ç¨æ¯x+1,x+4,x+9,x+16,x+25......,以æ¤ç±»æ¨ï¼å°åå§è·ç¦»çæ¥æ°å¹³æ¹ï¼ä¸ºäºæ¹ä¾¿çè§£ï¼æä»¬æ¥çä¸é¢è¿å¼ å¾ã

å¨çº¿æ§æ¢æµä¸æä»¬æ¾å°é±å¤å¤çåå¨ä½ç½®éè¦ç»è¿4æ¥ãå¨äºæ¬¡æ¢æµä¸ï¼æ¯æ¬¡æ¯åå§è·ç¦»æ¥æ°çå¹³æ¹ï¼æä»¥æä»¬åªéè¦ä¸¤æ¬¡å°±æ¾å°é±å¤å¤çåå¨ä½ç½®ã
03ãåå叿³ï¼å½åå¸å½æ°è®¡ç®åºæ¥çæ°ç»ä¸æ å·²ç»è¢«å ç¨æ¶ï¼å°±ä½¿ç¨å¦ä¸ä¸ªåå¸å½æ°è®¡ç®åºæ¥çæ°ç»ä¸æ ã
äºæ¬¡æ¢æµæ¶é¤äºçº¿æ§æ¢æµçèéé®é¢ï¼è¿ç§èéé®é¢å«ååå§èéï¼ç¶èï¼äºæ¬¡æ¢æµä¹äº§çäºæ°çèéé®é¢ï¼ä¹æä»¥ä¼äº§çæ°çèéé®é¢ï¼æ¯å ä¸ºæææ å°å°åä¸ä½ç½®çå ³é®åå¨å¯»æ¾ç©ºä½æ¶ï¼æ¢æµçä½ç½®é½æ¯ä¸æ ·çã
æ¯å¦è®²1ã11ã21ã31ã41便¬¡æå ¥å°åå¸è¡¨ä¸ï¼å®ä»¬æ å°çä½ç½®é½æ¯1ï¼é£ä¹11éè¦ä»¥ä¸ä¸ºæ¥é¿æ¢æµï¼21éè¦ä»¥å为æ¥é¿æ¢æµï¼31éè¦ä¸ºä¹ä¸ºæ¥é¿æ¢æµï¼41éè¦ä»¥åå 为æ¥é¿æ¢æµï¼åªè¦æä¸é¡¹æ å°å°1çä½ç½®ï¼å°±éè¦æ´é¿çæ¥é¿æ¥æ¢æµï¼è¿ä¸ªç°è±¡å«åäºæ¬¡èéã
åå叿³æ¯ä¸ºäºæ¶é¤åå§èéåäºæ¬¡èéé®é¢ï¼ä¸ç®¡æ¯çº¿æ§æ¢æµè¿æ¯äºæ¬¡æ¢æµï¼æ¯æ¬¡çæ¢æµæ¥é¿é½æ¯åºå®çãåå叿¯é¤äºç¬¬ä¸ä¸ªåå¸å½æ°å¤åå¢å ä¸ä¸ªåå¸å½æ°ç¨æ¥æ ¹æ®å ³é®åçææ¢æµæ¥é¿ï¼è¿æ ·å³ä½¿ç¬¬ä¸ä¸ªåå¸å½æ°æ å°å°äºæ°ç»çåä¸ä¸æ ï¼ä½æ¯æ¢æµæ¥é¿ä¸ä¸æ ·ï¼è¿æ ·å°±è½å¤è§£å³èéçé®é¢ã
第äºä¸ªåå¸å½æ°å¿ é¡»å ·å¤å¦ä¸ç¹ç¹ï¼
- å第ä¸ä¸ªåå¸å½æ°ä¸ä¸æ ·
- ä¸è½è¾åºä¸º 0ï¼å 为æ¥é¿ä¸º 0ï¼æ¯æ¬¡æ¢æµé½æ¯æååä¸ä¸ªä½ç½®ï¼å°è¿å
¥æ»å¾ªç¯ï¼ç»è¿è¯éªå¾åº
stepSize = constant-(key%constant);å½¢å¼çåå¸å½æ°ææé常好ï¼constantæ¯ä¸ä¸ªè´¨æ°å¹¶ä¸å°äºæ°ç»å®¹éã
示æå¾å¦ä¸ï¼

ThreadLocalMap æ¯ä½¿ç¨å¼æ¾å°åæ³æ¥å¤çåå¸å²çªçï¼å HashMap ä¸åï¼ä¹æä»¥éç¨ä¸åçæ¹å¼ä¸»è¦æ¯å 为ï¼
ThreadLocalMap ä¸çåå¸å¼åæ£çæ¯è¾ååï¼å¾å°ä¼åºç°å²çªãå¹¶ä¸ ThreadLocalMap ç»å¸¸éè¦æ¸ 餿 ç¨ç对象ï¼å²çªçæ¦çå°±æ´å°äºã
set æ¹æ³
好ï¼å¨äºè§£åå¸è¡¨çç¸å ³ç¥è¯åï¼æä»¬åæ¥çä¸ä¸ set æ¹æ³ãset æ¹æ³çæºç å¦ä¸ï¼
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
//æ ¹æ®ThreadLocalçhashCodeç¡®å®Entryåºè¯¥åæ¾çä½ç½®
int i = key.ThreadLocalHashCode & (len-1);
//éç¨å¼æ¾å°åæ³ï¼hashå²çªçæ¶å使ç¨çº¿æ§æ¢æµ
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//è¦çæ§Entry
if (k == key) {
e.value = value;
return;
}
//å½key为nullæ¶ï¼è¯´æThreadLocal强å¼ç¨å·²ç»è¢«éæ¾æï¼é£ä¹å°±æ æ³
//åéè¿è¿ä¸ªkeyè·åThreadLocalMapä¸å¯¹åºçentryï¼è¿éå°±åå¨å
åæ³æ¼çå¯è½æ§
if (k == null) {
//ç¨å½åæå
¥ç弿¿æ¢æè¿ä¸ªkey为nullçâèâentry
replaceStaleEntry(key, value, i);
return;
}
}
//æ°å»ºentryå¹¶æå
¥tableä¸iå¤
tab[i] = new Entry(key, value);
int sz = ++size;
//æå
¥å忬¡æ¸
é¤ä¸äºkey为nullçâèâentry,妿大äºéå¼å°±éè¦æ©å®¹
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}set æ¹æ³çå ³é®é¨åè¯·çæ³¨éï¼è¿éæå ç¹éè¦æ³¨æï¼
01ãThreadLocal ç hashcode
private final int ThreadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT = 0x61c88647;
private static AtomicInteger nextHashCode =new AtomicInteger();
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}ThreadLocal ç hashCode æ¯éè¿ nextHashCode() æ¹æ³è·åçï¼è¯¥æ¹æ³å®é
䏿¯ç¨ AtomicInteger å ä¸ 0x61c88647 æ¥å®ç°çã
0x61c88647 æ¯ä¸ä¸ªéæ°ï¼ç¨äº ThreadLocal çåå¸ç éå¢ãè¿ä¸ªå¼ç鿩并䏿¯éæºçï¼å®æ¯ä¸ä¸ªè´¨æ°ï¼å ·æä»¥ä¸ç¹æ§ï¼
- è´¨æ°ï¼å®æ¯ä¸ä¸ªè´¨æ°ï¼è¿æå³çå®ä¸è½è¢«é¤ 1 å宿¬èº«ä¹å¤ç任使°åæ´é¤ã
- é»éæ¯ä¾ï¼è¿ä¸ªæ°å大约çäºé»éæ¯ä¾ç 32 使µ®ç¹è¡¨ç¤ºçä¸åãé»éæ¯ä¾å ·æä¸äºæè¶£çæ°å¦ç¹æ§ï¼å ¶ä¸ä¹ä¸æ¯ä¸ææ³¢é£å¥æ°åçå ³ç³»ã
- éå¢åå¸ï¼å¨ ThreadLocal ä¸ï¼è¿ä¸ªæ°åç¨äºå¨åå¸è¡¨ä¸åæ£ä¸å线ç¨çåå¸ç ï¼ä»èåå°å²çªãæ¯å½å建æ°ç ThreadLocal 对象æ¶ï¼é½ä¼å°æ¤å¼æ·»å å°ä¸ä¸ä¸ª ThreadLocal çåå¸ç ä¸ãè¿ä¸ªéå¢çæ¥é¿æå©äºå¨åå¸è¡¨ä¸ååå°åé ThreadLocal 对象ã
- æ§è½ä¼åï¼éè¿ä½¿ç¨è¿ä¸ªç¹å®çå¼ï¼ç®æ³è½å¤ç¡®ä¿åå¸ç çåååå¸ï¼ä»èåå°åå¸å²çªçå¯è½æ§ãè¿å¯¹äºåå¸è¡¨çæ§è½è³å ³éè¦ï¼å 为å²çªå¯è½ä¼é使¥æ¾çæçã
02ãææ ·ç¡®å®æ°å¼æå ¥çä½ç½®ï¼
éè¿è¿è¡ä»£ç ï¼key.ThreadLocalHashCode & (len-1)ã
å HashMap 䏿 ·ï¼éè¿å½å key ç hashcode ä¸åå¸è¡¨å¤§å°ç¸ä¸ãåçæä»¬å¨ HashMap çæ¶åå·²ç»è®²è¿äºï¼ä¸è®°å¾çå°ä¼ä¼´å¯ä»¥åå»çä¸éã
03ãææ ·è§£å³ hash å²çªï¼
éè¿ nextIndex(i, len)ï¼è¯¥æ¹æ³ä¸ç((i + 1 < len) ? i + 1 : 0); è½ä¸æå¾åçº¿æ§æ¢æµï¼å½å°åå¸è¡¨æ«å°¾çæ¶ååä» 0 å¼å§ï¼æç¯å½¢ã
04ãææ ·è§£å³âèâEntryï¼
æä»¬ç¥éï¼ä½¿ç¨ ThreadLocal æå¯è½åå¨å åæ³æ¼çé®é¢ï¼é对è¿ç§ key 为 null ç Entryï¼æä»¬ç§°ä¹ä¸ºâstale entryâï¼ç´è¯ä¸ºä¸æ°é²ç entryï¼ææå®ç解为âè entryâã
å½ç¶äºï¼Josh Bloch å Doug Lea å·²ç»æ¿æä»¬èèäºè¿ç§æ åµï¼æºç 䏿ä¾äºè¿äºè§£å³æ¹æ¡ï¼
å¨åThreadLocalMapæ·»å æ°æ¡ç®æ¶ï¼å¯ä»¥æ£æ¥æ¯å¦æâèâEntryï¼é®ä¸ºnullçEntryï¼ï¼å¹¶ç¨æ°çæ¡ç®æ¿æ¢å®ãè¿å°±æ¯æºç ä¸çreplaceStaleEntryæ¹æ³æåçäºæ ã

å¨æäºæä½è¿ç¨ä¸ï¼ä¾å¦æ·»å ãè·åçï¼ï¼å¯ä»¥å¢å é¢å¤çæ¸ çæä½æ¥æ«æå¹¶ç§»é¤âèâEntryãè¿å¯ä»¥éè¿éååå¸è¡¨ï¼å¹¶å é¤é£äºé®ä¸ºnullçæ¡ç®æ¥å®ç°ãæºç ä¸çcleanSomeSlotsæ¹æ³å°±æ¯è¿æ ·ä¸ä¸ªä¾åã

05ãå¦ä½è¿è¡æ©å®¹ï¼
å HashMap 䏿 ·ï¼ThreadLocalMap 乿æ©å®¹æºå¶ï¼é£ä¹å®ç threshold åæ¯ææ ·ç¡®å®çå¢ï¼
private int threshold; // Default to 0
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.ThreadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}å¨ç¬¬ä¸æ¬¡å¯¹ ThreadLocal èµå¼çæ¶åä¼å建åå§å¤§å°ä¸º 16 ç ThreadLocalMapï¼å¹¶ä¸éè¿ setThreshold æ¹æ³è®¾ç½® thresholdï¼å ¶å¼ä¸ºå½åå叿°ç»é¿åº¦ä¹ä»¥ï¼2/3ï¼ï¼ä¹å°±æ¯è¯´å è½½å å为 2/3ã
å è½½å åï¼Load Factorï¼æ¯åå¸è¡¨çä¸ä¸ªéè¦æ¦å¿µï¼å®è¡¨ç¤ºåå¸è¡¨ä¸å·²ç»åæ¾çæ¡ç®æ°éä¸åå¸è¡¨å®¹éçæ¯ä¾ãå è½½å åå¯ä»¥ç¨æ¥è¡¡éåå¸è¡¨ç满载ç¨åº¦ï¼å½±ååå¸è¡¨çæ¥æ¾ãæå ¥åå 餿ä½çæ§è½ãç¸ä¿¡å¤§å®¶é½è¿è®°å¾ï¼HashMap çå è½½å åé½ä¸º 0.75ã
è¿éThreadLocalMap åå§å¤§å°ä¸º 16ï¼å è½½å å为 2/3ï¼æä»¥åå¸è¡¨å¯ç¨å¤§å°ä¸ºï¼16*2/3=10ï¼å³åå¸è¡¨å¯ç¨å®¹é为 10ã
å½åå¸è¡¨ç size å¤§äº threshold çæ¶åï¼ä¼éè¿ resize æ¹æ³è¿è¡æ©å®¹ã
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
//æ°æ°ç»ä¸ºåæ°ç»ç2å
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
//éåè¿ç¨ä¸å¦æéå°èentryçè¯ç´æ¥å¦value为null,æå©äºvalueè½å¤è¢«åæ¶
if (k == null) {
e.value = null; // Help the GC
} else {
//éæ°ç¡®å®entry卿°æ°ç»çä½ç½®ï¼ç¶åè¿è¡æå
¥
int h = k.ThreadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
//设置æ°åå¸è¡¨çthreshHoldåsize屿§
setThreshold(newLen);
size = count;
table = newTab;
}æ¹æ³é»è¾è¯·ç注éï¼æ°å»ºçæ°ç»ä¸ºåæ¥æ°ç»é¿åº¦ç两åï¼ç¶åéåæ§æ°ç»ä¸ç entry å¹¶å°å ¶æå ¥å°æ°çæ°ç»ä¸ã注æï¼è¿æ®µä»£ç èèå¾é常å¨å ¨ï¼å¨æ©å®¹çè¿ç¨ä¸ï¼é对è entry 伿 value 设为 nullï¼ä»¥ä¾¿è¢«åå¾åæ¶ï¼è§£å³éèçå åæ³æ¼é®é¢ã
getEntry æ¹æ³
getEntry æ¹æ³çæºç å¦ä¸ï¼
private Entry getEntry(ThreadLocal<?> key) {
//1. ç¡®å®å¨å叿°ç»ä¸çä½ç½®
int i = key.ThreadLocalHashCode & (table.length - 1);
//2. æ ¹æ®ç´¢å¼iè·åentry
Entry e = table[i];
//3. 满足æ¡ä»¶åè¿å该entry
if (e != null && e.get() == key)
return e;
else
//4. æªæ¥æ¾å°æ»¡è¶³æ¡ä»¶çentryï¼é¢å¤å¨åçå¤ç
return getEntryAfterMiss(key, i, e);
}æ¹æ³çé»è¾å¾ç®åï¼å¦æå½å entry ç key 忥æ¾ç key ç¸åå°±ç´æ¥è¿åè¿ä¸ª entryï¼å¦åçå°±éè¿ getEntryAfterMiss åè¿ä¸æ¥å¤çï¼å¦æç´¢å¼å¤çæ¡ç®ä¸ºnullï¼æè å ¶é®ä¸ç»å®çé®ä¸å¹é ï¼é£ä¹éè¦è°ç¨getEntryAfterMissæ¹æ³æ¥å¤çå¯è½çåå¸å²çªã
getEntryAfterMiss æ¹æ³å¦ä¸ï¼
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
//æ¾å°åæ¥è¯¢çkeyç¸åçentryåè¿å
return e;
if (k == null)
//è§£å³èentryçé®é¢
expungeStaleEntry(i);
else
//ç»§ç»ååç¯å½¢æ¥æ¾
i = nextIndex(i, len);
e = tab[i];
}
return null;
}getEntryAfterMiss æ¹æ³ç¨äºå¨åçåå¸å²çªçæ åµä¸ç»§ç»å¨ThreadLocalMap䏿¥æ¾æ¡ç®ï¼éè¿å¼æ¾å¯»åççç¥ï¼å¨åå¸è¡¨ä¸çå ¶ä»ä½ç½®æ¥æ¾ï¼å¹¶éå½å°å¤çâèâæ¡ç®ã
remove æ¹æ³
ç´æ¥æ¥çæºç ï¼
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.ThreadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//å°entryçkey置为null
e.clear();
//å°è¯¥entryçvalueä¹ç½®ä¸ºnull
expungeStaleEntry(i);
return;
}
}
}01ãéè¿å±é¨åétabè·åThreadLocalMapçåå¸è¡¨æ°ç»ï¼lenè¡¨ç¤ºå ¶é¿åº¦ã
02ãéè¿key.ThreadLocalHashCode & (len-1)计ç®ç»å®ThreadLocalé®çåå¸ç´¢å¼ãè¿å°å³å®ä»åªä¸ªç´¢å¼ä½ç½®å¼å§æç´¢ã
03ã使ç¨å¼æ¾å¯»åæ³éååå¸è¡¨ï¼éè¿nextIndex(i, len)计ç®ä¸ä¸ä¸ªç´¢å¼ä»¥å¤çåå¸å²çªã
04ã妿æ¾å°ä¸ç»å®é®å¹é
çæ¡ç®ï¼å³e.get() == keyï¼ï¼æ§è¡ä»¥ä¸æä½ï¼
- æ¸
é¤é®ï¼éè¿è°ç¨
e.clear()æ¹æ³ï¼å°æ¡ç®çé®ç½®ä¸ºnullãç±äºEntryæ¯WeakReferenceçåç±»ï¼clearæ¹æ³å°æå¼å¯¹ThreadLocal对象çå¼ç¨ï¼å 许å徿¶éå¨å¨éè¦æ¶åæ¶å®ã - æ¸
é¤å¼ï¼éè¿è°ç¨
expungeStaleEntry(i)æ¹æ³ï¼æ¸ é¤è¯¥æ¡ç®çå¼å¹¶å¯¹åå¸è¡¨è¿è¡é¨åæ¸ çãè¯¥æ¹æ³çç®çæ¯æ¸ é¤åå¸è¡¨ä¸çæ ææ¡ç®ï¼å³é£äºå ¶é®å·²è¢«å徿¶éçæ¡ç®ã
05ãç»æå 餿ä½ï¼ä¸æ¦æ¾å°å¹¶å é¤äºå¹é çæ¡ç®ï¼æ¹æ³è¿åã妿éåæ´ä¸ªåå¸è¡¨é½æ²¡ææ¾å°å¹é çé®ï¼åè¯¥æ¹æ³ä¸æ§è¡ä»»ä½æä½å¹¶æ£å¸¸è¿åã
ThreadLocal ç使ç¨åºæ¯
ThreadLocal ç使ç¨åºæ¯é常å¤ï¼æ¯å¦è¯´ï¼
- ç¨äºä¿åç¨æ·ç»å½ä¿¡æ¯ï¼è¿æ ·å¨åä¸ä¸ªçº¿ç¨ä¸çä»»ä½å°æ¹é½å¯ä»¥è·åå°ç»å½ä¿¡æ¯ã
- ç¨äºä¿åæ°æ®åºè¿æ¥ãSession 对象çï¼è¿æ ·å¨åä¸ä¸ªçº¿ç¨ä¸çä»»ä½å°æ¹é½å¯ä»¥è·åå°æ°æ®åºè¿æ¥ãSession 对象çã
- ç¨äºä¿åäºå¡ä¸ä¸æï¼è¿æ ·å¨åä¸ä¸ªçº¿ç¨ä¸çä»»ä½å°æ¹é½å¯ä»¥è·åå°äºå¡ä¸ä¸æã
- ç¨äºä¿å线ç¨ä¸çåéï¼è¿æ ·å¨åä¸ä¸ªçº¿ç¨ä¸çä»»ä½å°æ¹é½å¯ä»¥è·åå°çº¿ç¨ä¸çåéã
ä¸é¢æ¯ä¸ä¸ªä½¿ç¨ThreadLocalæ¥ä¿åç¨æ·ç»å½ä¿¡æ¯ç示ä¾ãè¿ä¸ªç¤ºä¾éç¨äºåWebæå¡å¨è¿æ ·çå¤çº¿ç¨ç¯å¢ï¼å ¶ä¸æ¯ä¸ªçº¿ç¨å¤çä¸ä¸ªç¬ç«çç¨æ·è¯·æ±ã
public class UserAuthenticationService {
// å建ä¸ä¸ªThreadLocalå®ä¾ï¼ç¨äºä¿åç¨æ·ç»å½ä¿¡æ¯
private static ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);
public static void main(String[] args) {
// 模æç¨æ·ç»å½
loginUser(new User("Alice", "password123"));
System.out.println("User logged in: " + getCurrentUser().getUsername());
// 模æå¦ä¸ä¸ªçº¿ç¨å¤çå¦ä¸ä¸ªç¨æ·
Runnable task = () -> {
loginUser(new User("Bob", "password456"));
System.out.println("User logged in: " + getCurrentUser().getUsername());
};
Thread thread = new Thread(task);
thread.start();
}
// 模æç¨æ·ç»å½æ¹æ³
public static void loginUser(User user) {
// è¿éé叏伿ä¸äºèº«ä»½éªè¯é»è¾
currentUser.set(user);
}
// è·åå½å线ç¨å
³èçç¨æ·ä¿¡æ¯
public static User getCurrentUser() {
return currentUser.get();
}
// ç¨æ·ç±»
public static class User {
private final String username;
private final String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
// å
¶ä»getteråsetter...
}
}è¿ä¸ªç¤ºä¾å®ä¹äºä¸ä¸ªUserAuthenticationServiceç±»ï¼è¯¥ç±»ä½¿ç¨ThreadLocalæ¥ä¿åä¸å½å线ç¨å ³èçç¨æ·ç»å½ä¿¡æ¯ãåè®¾ç¨æ·å·²ç»éè¿èº«ä»½éªè¯ï¼å°ç¨æ·å¯¹è±¡åå¨å¨currentUser ThreadLocalåéä¸ãgetCurrentUseræ¹æ³ç¨äºæ£ç´¢ä¸å½å线ç¨å ³èçç¨æ·ä¿¡æ¯ãç±äºä½¿ç¨äºThreadLocalï¼å æ¤ä¸åç线ç¨å¯ä»¥åæ¶ç»å½ä¸åçç¨æ·ï¼èä¸ä¼ç¸äºå¹²æ°ã
å°ç»
ThreadLocal æ¯ä¸ä¸ªé常æç¨çå·¥å ·ç±»ï¼å®å¯ä»¥ç¨äºä¿å线ç¨ä¸çåéï¼è¿æ ·å¨åä¸ä¸ªçº¿ç¨ä¸çä»»ä½å°æ¹é½å¯ä»¥è·åå°çº¿ç¨ä¸çåéã使¯ï¼ThreadLocal 乿¯ä¸ä¸ªé常容æè¢«è¯¯ç¨çå·¥å ·ç±»ï¼å¦ææ²¡æä½¿ç¨å¥½ï¼å°±å¯è½ä¼é æå åæ³æ¼çé®é¢ã
ThreadLocalMap æ¯ ThreadLocal çæ ¸å¿ï¼å®æ¯ä¸ä¸ªä»¥ ThreadLocal å®ä¾ä¸º keyï¼ä»»æå¯¹è±¡ä¸º value çåå¸è¡¨ãThreadLocalMap 使ç¨å¼æ¾å°åæ³æ¥å¤çåå¸å²çªï¼å®çåå§å®¹é为 16ï¼å è½½å å为 2/3ï¼æ©å®¹æ¶ä¼å°å®¹éæ©å¤§ä¸ºåæ¥ç两åã
ç¼è¾ï¼æ²é»çäºï¼é¨åå 容æ¥èªäºCL0610ç GitHub ä»åºhttps://github.com/CL0610/Java-concurrencyï¼é¨åå¾çåå 容æ¥èµæºç¥ä¹è¿ç¯å¸åã
GitHub 䏿 æ 10000+ ç弿ºç¥è¯åºãäºå¥ç Java è¿é¶ä¹è·¯ã第äºä»½ PDF ãå¹¶åç¼ç¨å°åãç»äºæ¥äºï¼å æ¬çº¿ç¨çåºæ¬æ¦å¿µåä½¿ç¨æ¹æ³ãJavaçå 忍¡åãsychronizedãvolatileãCASãAQSãReentrantLockãçº¿ç¨æ± ãå¹¶å容å¨ãThreadLocalãçäº§è æ¶è´¹è 模åçé¢è¯åå¼åå¿ é¡»ææ¡çå 容ï¼å ±è®¡ 15 ä¸ä½åï¼200+å¼ æç»å¾ï¼å¯ä»¥è¯´æ¯éä¿ææãé£è¶£å¹½é»â¦â¦è¯¦æ æ³ï¼å¤ªèµäºï¼äºå¥çå¹¶åç¼ç¨è¿é¶ä¹è·¯.pdf
å å ¥äºå¥çç¼ç¨æçï¼å¨æçç第äºä¸ªç½®é¡¶å¸ãç¥è¯å¾è°±ãéå°±å¯ä»¥è·å PDF çæ¬ã

