जावा के थ्रेडलोक को हुड के नीचे कैसे लागू किया गया है?


81

थ्रेडलोक को कैसे लागू किया जाता है? क्या इसे जावा में लागू किया गया है (थ्रेडिड से ऑब्जेक्ट तक कुछ समवर्ती मानचित्र का उपयोग करके), या क्या यह अधिक कुशलतापूर्वक करने के लिए कुछ जेवीएम हुक का उपयोग करता है?

जवाबों:


120

यहाँ के सभी उत्तर सही हैं, लेकिन थोड़ा निराशाजनक है क्योंकि वे कुछ हद तक इस बात पर प्रकाश डालते हैं कि यह कितना चतुर ThreadLocalहै। मैं सिर्फ स्रोत कोडThreadLocal को देख रहा था और इसे लागू करने के तरीके से खुश था।

द नाइव इम्प्लीमेंटेशन

अगर मैंने आपसे ThreadLocal<T>javadoc में वर्णित एपीआई को दिए गए वर्ग को लागू करने के लिए कहा , तो आप क्या करेंगे? एक प्रारंभिक कार्यान्वयन इसकी कुंजी के रूप में ConcurrentHashMap<Thread,T>उपयोग करने की संभावना होगी Thread.currentThread()। यह यथोचित काम करेगा, लेकिन इसके कुछ नुकसान भी हैं।

  • धागा विवाद - ConcurrentHashMapएक सुंदर स्मार्ट क्लास है, लेकिन अंततः इसे अभी भी कई थ्रेड्स को किसी भी तरह से इसके साथ मिलाने से रोकना पड़ता है, और अगर अलग-अलग धागे इसे नियमित रूप से मारते हैं, तो मंदी होगी।
  • स्थायी रूप से थ्रेड और ऑब्जेक्ट दोनों के लिए एक पॉइंटर रखता है, भले ही थ्रेड समाप्त हो गया हो और GC'ed हो सकता है।

जीसी के अनुकूल कार्यान्वयन

ठीक है, फिर से कोशिश करें, कमज़ोर संदर्भों का उपयोग करके कचरा संग्रहण के मुद्दे से निपटें । 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 के कार्यान्वयन


1
आपके उत्तर भयानक लग रहे हैं, लेकिन यह अभी मेरे लिए बहुत लंबा है। +1 और स्वीकार किया, और मैं इसे बाद में पढ़ने के लिए अपने getpocket.com खाते में जोड़ रहा हूं। धन्यवाद!
ripper234

मुझे थ्रेडलोक-जैसी चीज़ की आवश्यकता है जो मानों की पूरी सूची तक पहुंचने देता है, जैसे कि map.values ​​()। तो, मेरा अनुभवहीन कार्यान्वयन एक WeakHashMap <स्ट्रिंग, ऑब्जेक्ट> है जहां कुंजी थ्रेड है ।.creadTread ()। getName ()। यह थ्रेड के संदर्भ से बचा जाता है। यदि थ्रेड चला जाता है, तो कुछ भी थ्रेड के नाम को नहीं पकड़ रहा है (एक धारणा, मैं मानता हूं) और मेरा मूल्य दूर हो जाएगा।
बारमार्ट

मैंने वास्तव में उस प्रभाव के एक प्रश्न का उत्तर हाल ही में दिया । एक WeakHashMap<String,T>कई समस्याओं का परिचय देता है, यह थ्रेडसेफ़ नहीं है, और यह "मुख्य रूप से उन महत्वपूर्ण वस्तुओं के साथ उपयोग करने के लिए है, जिनकी विधि विधियों का = = ऑपरेटर" का उपयोग करके ऑब्जेक्ट पहचान के लिए परीक्षण किया जाता है - इसलिए वास्तव Threadमें एक कुंजी के रूप में ऑब्जेक्ट का उपयोग करना बेहतर होगा। मैं आपको अपने उपयोग के मामले के लिए ऊपर वर्णित अमरूद कमजोर मानचित्र का उपयोग करने का सुझाव दूंगा।
dimo414

1
अच्छी तरह से कमजोर कुंजियाँ आवश्यक नहीं हैं, लेकिन समकालिक हाशम पर एक समवर्ती हाशपा का उपयोग करने पर विचार करें - पूर्व को बहु-थ्रेडेड एक्सेस के लिए डिज़ाइन किया गया है और इस मामले में बहुत बेहतर काम करेगा जहां प्रत्येक थ्रेड आमतौर पर एक अलग कुंजी का उपयोग कर रहा है।
dimo414

1
@shmosel इस वर्ग को उच्च स्तरीय है , इसलिए मैं इस धारणा से शुरू करूंगा कि इस तरह के विचार किए गए हैं। एक त्वरित नज़र से पता चलता है कि जब एक थ्रेड सामान्य रूप Thread.exit()से समाप्त हो जाता है, तो आपको बुलाया जाएगा और आप threadLocals = null;वहीं देखेंगे । एक टिप्पणी इस बग का संदर्भ देती है जिसे पढ़कर आपको भी आनंद आ सकता है।
dimo414

33

आपका मतलब है java.lang.ThreadLocal। यह काफी सरल है, वास्तव में, यह प्रत्येक Threadऑब्जेक्ट के अंदर संग्रहीत नाम-मूल्य जोड़े का मानचित्र है ( Thread.threadLocalsफ़ील्ड देखें )। एपीआई उस कार्यान्वयन विस्तार को छुपाता है, लेकिन यह कमोबेश सभी को है।


मैं यह नहीं देख सकता कि किसी की आवश्यकता क्यों होनी चाहिए, यह देखते हुए कि परिभाषा के अनुसार डेटा केवल एक ही धागे में दिखाई देता है।
skaffman

8
सही है, थ्रेडलोकेशन मैप के आसपास या भीतर कोई सिंक्रोनाइज़ेशन या लॉकिंग नहीं है, क्योंकि यह केवल थ्रेड में एक्सेस किया गया है।
कोवान

8

Java में ThreadLocal चर, Thread.currentThread () उदाहरण द्वारा आयोजित एक HashMap तक पहुँच कर काम करता है।


यह सही नहीं है (या कम से कम यह अब नहीं है)। Thread.currentThread () Thread.class में एक देशी कॉल है। साथ ही थ्रेड में एक "थ्रेडलोकल मैप" है जो हैश कोड का एकल-बकेट (सरणी) है। यह ऑब्जेक्ट मैप इंटरफ़ेस का समर्थन नहीं करता है।
user924272

1
मूल रूप से यही मैंने कहा था। currentThread () एक थ्रेड इंस्टेंस देता है, जो थ्रेडलोकल्स का एक नक्शा मानों पर रखता है।
क्रिस वेस्ट

4

मान लीजिए आप कार्यान्वित करने जा रहे हैं 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();
}

पूरी तस्वीर में ऐसा ही है:

पूरी तस्वीर


-1

वैचारिक रूप से, आप यह सोच सकते हैं कि एक थ्रेड ThreadLocal<T>को रखने Map<Thread,T>से थ्रेड-स्पाई values ​​सी वैल्यू स्टोर हो जाता है, हालांकि यह नहीं है कि यह वास्तव में कैसे लागू किया जाता है।

थ्रेड-स्पीसी thread c मान थ्रेड ऑब्जेक्ट में ही संग्रहीत होते हैं; जब थ्रेड समाप्त हो जाता है, तो थ्रेड-स्पैसी can सी मान कचरा एकत्र किया जा सकता है।

संदर्भ: जेसीआईपी


1
वैचारिक रूप से, हाँ। लेकिन जैसा कि आप देखते हैं कि कार्यान्वयन के ऊपर अन्य उत्तर पूरी तरह से अलग हैं।
अर्चित
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.