थ्रेडलोक को कैसे लागू किया जाता है? क्या इसे जावा में लागू किया गया है (थ्रेडिड से ऑब्जेक्ट तक कुछ समवर्ती मानचित्र का उपयोग करके), या क्या यह अधिक कुशलतापूर्वक करने के लिए कुछ जेवीएम हुक का उपयोग करता है?
जवाबों:
यहाँ के सभी उत्तर सही हैं, लेकिन थोड़ा निराशाजनक है क्योंकि वे कुछ हद तक इस बात पर प्रकाश डालते हैं कि यह कितना चतुर ThreadLocal
है। मैं सिर्फ स्रोत कोडThreadLocal
को देख रहा था और इसे लागू करने के तरीके से खुश था।
द नाइव इम्प्लीमेंटेशन
अगर मैंने आपसे ThreadLocal<T>
javadoc में वर्णित एपीआई को दिए गए वर्ग को लागू करने के लिए कहा , तो आप क्या करेंगे? एक प्रारंभिक कार्यान्वयन इसकी कुंजी के रूप में ConcurrentHashMap<Thread,T>
उपयोग करने की संभावना होगी Thread.currentThread()
। यह यथोचित काम करेगा, लेकिन इसके कुछ नुकसान भी हैं।
ConcurrentHashMap
एक सुंदर स्मार्ट क्लास है, लेकिन अंततः इसे अभी भी कई थ्रेड्स को किसी भी तरह से इसके साथ मिलाने से रोकना पड़ता है, और अगर अलग-अलग धागे इसे नियमित रूप से मारते हैं, तो मंदी होगी।जीसी के अनुकूल कार्यान्वयन
ठीक है, फिर से कोशिश करें, कमज़ोर संदर्भों का उपयोग करके कचरा संग्रहण के मुद्दे से निपटें । WeakReferences के साथ काम करना भ्रामक हो सकता है, लेकिन इसे बनाए गए नक्शे का उपयोग करने के लिए पर्याप्त होना चाहिए:
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
या अगर हम अमरूद का उपयोग कर रहे हैं (और हमें होना चाहिए!):
new MapMaker().weakKeys().makeMap()
इसका मतलब यह है कि एक बार थ्रेड पर कोई भी पकड़ नहीं रहा है (यह समाप्त हो गया है) कुंजी / मान एकत्र किया जा सकता है, जो एक सुधार है, लेकिन फिर भी थ्रेड कंटेक्शन मुद्दे को संबोधित नहीं करता है, जिसका अर्थ है कि अब तक ThreadLocal
यह सब नहीं है एक वर्ग का कमाल। इसके अलावा, अगर किसी ने Thread
वस्तुओं को धारण करने के बाद समाप्त करने का फैसला किया, तो वे GC'ed कभी नहीं होंगे, और इसलिए न तो हमारी वस्तुएं होंगी, भले ही वे अब तकनीकी रूप से अप्राप्य हों।
चतुर कार्यान्वयन
हम ThreadLocal
मानों को थ्रेडिंग के मानचित्रण के रूप में सोच रहे हैं, लेकिन शायद यह वास्तव में इसके बारे में सोचने का सही तरीका नहीं है। प्रत्येक थ्रेडलोक ऑब्जेक्ट में थ्रेड्स से मानों तक मैपिंग के रूप में सोचने के बजाय, यदि हम थ्रेडलोक ऑब्जेक्ट्स की मैपिंग के रूप में प्रत्येक थ्रेड में मानों के बारे में सोचते हैं, तो क्या होगा ? यदि प्रत्येक थ्रेड मैपिंग को संग्रहीत करता है, और थ्रेडलॉक केवल उस मैपिंग में एक अच्छा इंटरफ़ेस प्रदान करता है, तो हम पिछले कार्यान्वयन के सभी मुद्दों से बच सकते हैं।
एक कार्यान्वयन कुछ इस तरह दिखेगा:
// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()
यहाँ संगति के बारे में चिंता करने की कोई आवश्यकता नहीं है, क्योंकि केवल एक धागा कभी इस नक्शे तक पहुंच जाएगा।
जावा देवों का हमारे ऊपर एक बड़ा फायदा है - वे सीधे थ्रेड क्लास को विकसित कर सकते हैं और इसमें फ़ील्ड और ऑपरेशन जोड़ सकते हैं, और ठीक यही उन्होंने किया है।
में java.lang.Thread
निम्नलिखित लाइनों नहीं है:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
जैसा कि टिप्पणी से पता चलता है कि वास्तव में इसके ThreadLocal
लिए वस्तुओं द्वारा ट्रैक किए जा रहे सभी मूल्यों का एक पैकेज-निजी मानचित्रण है Thread
। का कार्यान्वयन ThreadLocalMap
एक नहीं है WeakHashMap
, लेकिन यह एक ही मूल अनुबंध का पालन करता है, जिसमें कमजोर संदर्भ द्वारा इसकी चाबियाँ पकड़ना शामिल है।
ThreadLocal.get()
फिर इसे इस तरह लागू किया जाता है:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
और ThreadLocal.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;
}
अनिवार्य रूप से, हमारे सभी ऑब्जेक्ट्स को होल्ड करने के लिए इस थ्रेड में एक मैप का उपयोग करें ThreadLocal
। इस तरह, हमें कभी भी अन्य थ्रेड्स में मूल्यों के बारे में चिंता करने की आवश्यकता नहीं है ( ThreadLocal
शाब्दिक रूप से केवल वर्तमान थ्रेड में मानों का उपयोग किया जा सकता है) और इसलिए कोई समसामयिक मुद्दे नहीं हैं। इसके अलावा, एक बार हो जाने के बाद Thread
, इसका मानचित्र स्वचालित रूप से GC'ed हो जाएगा और सभी स्थानीय वस्तुओं को साफ कर दिया जाएगा। यहां तक कि अगर Thread
पर रखा गया है, तो ThreadLocal
वस्तुओं को कमजोर संदर्भ द्वारा आयोजित किया जाता है, और ThreadLocal
ऑब्जेक्ट को दायरे से बाहर जाते ही साफ किया जा सकता है ।
कहने की जरूरत नहीं है, मैं इस कार्यान्वयन से प्रभावित था, यह काफी सुरुचिपूर्ण ढंग से कई संगीन मुद्दों के आसपास हो जाता है (मूल रूप से कोर जावा का हिस्सा होने का लाभ उठाकर, लेकिन यह उन्हें माफ कर देता है क्योंकि यह एक चतुर वर्ग है) और उपवास की अनुमति देता है उन वस्तुओं तक धागा-सुरक्षित पहुंच जो केवल एक समय में एक धागे से एक्सेस करने की आवश्यकता होती है।
tl; डॉ ThreadLocal
का कार्यान्वयन बहुत अच्छा है, और बहुत तेज़ / होशियार है जितना आप पहली नज़र में सोच सकते हैं।
यदि आपको यह उत्तर पसंद आया है तो आप मेरी (कम विस्तृत) चर्चा कीThreadLocalRandom
सराहना कर सकते हैं ।
Thread
/ ThreadLocal
कोड के टुकड़े से लिया जावा 8 के ओरेकल / OpenJDK के कार्यान्वयन ।
WeakHashMap<String,T>
कई समस्याओं का परिचय देता है, यह थ्रेडसेफ़ नहीं है, और यह "मुख्य रूप से उन महत्वपूर्ण वस्तुओं के साथ उपयोग करने के लिए है, जिनकी विधि विधियों का = = ऑपरेटर" का उपयोग करके ऑब्जेक्ट पहचान के लिए परीक्षण किया जाता है - इसलिए वास्तव Thread
में एक कुंजी के रूप में ऑब्जेक्ट का उपयोग करना बेहतर होगा। मैं आपको अपने उपयोग के मामले के लिए ऊपर वर्णित अमरूद कमजोर मानचित्र का उपयोग करने का सुझाव दूंगा।
Thread.exit()
से समाप्त हो जाता है, तो आपको बुलाया जाएगा और आप threadLocals = null;
वहीं देखेंगे । एक टिप्पणी इस बग का संदर्भ देती है जिसे पढ़कर आपको भी आनंद आ सकता है।
आपका मतलब है java.lang.ThreadLocal
। यह काफी सरल है, वास्तव में, यह प्रत्येक Thread
ऑब्जेक्ट के अंदर संग्रहीत नाम-मूल्य जोड़े का मानचित्र है ( Thread.threadLocals
फ़ील्ड देखें )। एपीआई उस कार्यान्वयन विस्तार को छुपाता है, लेकिन यह कमोबेश सभी को है।
Java में ThreadLocal चर, Thread.currentThread () उदाहरण द्वारा आयोजित एक HashMap तक पहुँच कर काम करता है।
मान लीजिए आप कार्यान्वित करने जा रहे हैं ThreadLocal
, तो आप इसे थ्रेड-विशिष्ट कैसे बनाते हैं? बेशक सबसे आसान तरीका थ्रेड क्लास में एक गैर-स्थिर क्षेत्र बनाना है, चलो इसे कॉल करें threadLocals
। क्योंकि प्रत्येक थ्रेड को थ्रेड आवृत्ति द्वारा दर्शाया जाता है, इसलिए threadLocals
प्रत्येक थ्रेड में भी भिन्न होगा। और यह भी जावा क्या करता है:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap
यहाँ क्या है ? क्योंकि आपके पास केवल threadLocals
एक धागे के लिए है, इसलिए यदि आप बस threadLocals
अपने अनुसार लेते हैं ThreadLocal
(जैसे, थ्रेडलोकल्स को परिभाषित करें Integer
), तो आपके पास केवल ThreadLocal
एक विशिष्ट धागा होगा। क्या होगा यदि आप ThreadLocal
एक धागे के लिए कई चर चाहते हैं ? सबसे आसान तरीका है बनाने के लिए है threadLocals
एक HashMap
, key
प्रत्येक प्रविष्टि का का नाम है ThreadLocal
चर, और value
प्रत्येक प्रविष्टि के का मूल्य है ThreadLocal
चर। थोड़ा भ्रमित? मान लें कि हमारे पास दो धागे हैं, t1
और t2
। वे कंस्ट्रक्टर Runnable
के पैरामीटर के रूप में एक ही उदाहरण लेते हैं Thread
, और उनके पास दो ThreadLocal
चर नाम हैं tlA
और tlb
। यह ऐसा ही है।
t1.tlA
+-----+-------+
| Key | Value |
+-----+-------+
| tlA | 0 |
| tlB | 1 |
+-----+-------+
t2.tlB
+-----+-------+
| Key | Value |
+-----+-------+
| tlA | 2 |
| tlB | 3 |
+-----+-------+
ध्यान दें कि मूल्य मेरे द्वारा बनाए गए हैं।
अब यह सही लगता है। लेकिन क्या है ThreadLocal.ThreadLocalMap
? यह सिर्फ उपयोग क्यों नहीं किया HashMap
? समस्या को हल करने के लिए, आइए देखें कि जब हम कक्षा की set(T value)
विधि के माध्यम से एक मूल्य निर्धारित करते हैं तो क्या होता है ThreadLocal
:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
getMap(t)
बस लौटता है t.threadLocals
। क्योंकि t.threadLocals
के लिए initilized गया था null
, इसलिए हम में प्रवेश createMap(t, value)
पहले:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
यह ThreadLocalMap
वर्तमान ThreadLocal
उदाहरण और सेट होने के मूल्य का उपयोग करके एक नया उदाहरण बनाता है । आइए देखें कि क्या ThreadLocalMap
पसंद है, यह वास्तव में ThreadLocal
कक्षा का हिस्सा है
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
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);
}
...
}
ThreadLocalMap
कक्षा का मुख्य भाग है Entry class
, जो विस्तार करता है WeakReference
। यह सुनिश्चित करता है कि यदि वर्तमान थ्रेड बाहर निकलता है, तो यह स्वचालित रूप से एकत्रित कचरा होगा। यही कारण है कि यह ThreadLocalMap
एक साधारण के बजाय उपयोग करता है HashMap
। यह कक्षा ThreadLocal
के पैरामीटर के रूप में वर्तमान और उसके मूल्य को पारित करता है Entry
, इसलिए जब हम मूल्य प्राप्त करना चाहते हैं, तो हम इसे प्राप्त कर सकते हैं table
, जो Entry
वर्ग का एक उदाहरण है :
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
पूरी तस्वीर में ऐसा ही है:
वैचारिक रूप से, आप यह सोच सकते हैं कि एक थ्रेड ThreadLocal<T>
को रखने Map<Thread,T>
से थ्रेड-स्पाई values सी वैल्यू स्टोर हो जाता है, हालांकि यह नहीं है कि यह वास्तव में कैसे लागू किया जाता है।
थ्रेड-स्पीसी thread c मान थ्रेड ऑब्जेक्ट में ही संग्रहीत होते हैं; जब थ्रेड समाप्त हो जाता है, तो थ्रेड-स्पैसी can सी मान कचरा एकत्र किया जा सकता है।
संदर्भ: जेसीआईपी