मुख्य सामग्री पर जाएँ

CooLWJ पर खोजें

选择搜索范围,输入关键词后按回车。

भाषा

Java थ्रेड कॉन्टेक्स्ट

ThreadLocal, JDK के java.lang पैकेज की एक प्रचलित उपयोगिता क्लास है, जो थ्रेड कॉन्टेक्स्ट की सुविधा प्रदान करती है। इसका सबसे आम उपयोग संभवतः लॉगिन के बाद उपयोगकर्ता के कॉन्टेक्स्ट जैसी महत्त्वपूर्ण जानकारी को संग्रहित करना है।

थ्रेड कॉन्टेक्स्ट ऐसी कई समस्याओं को भी सहज ढंग से हल कर सकता है जो देखने में जटिल लगती हैं, जैसे:spring-mybatis फ़्रेमवर्क का एकीकरण:
spring पारितंत्र पर आधारित विकास करते समय हम आम तौर पर ट्रांज़ैक्शन का प्रबंधन spring को सौंप देते हैं। transactional एनोटेशन के जरिए ट्रांज़ैक्शन चालू करने, उसका आइसोलेशन स्तर आदि की जानकारी कॉन्फ़िगर की जाती है। हम डेटाबेस पर काम भी सीधे jdbc से नहीं करते, बल्कि mybatis फ़्रेमवर्क के माध्यम से डेटाबेस तक पहुँचते हैं। spring और mybatis दो पूरी तरह स्वतंत्र फ़्रेमवर्क हैं, लेकिन इस्तेमाल करते समय लगता है कि spring द्वारा खोला गया डेटाबेस connection सीधे mybatis को मिल जाता है और वह उसका उपयोग कर सकता है। हमें लगभग कुछ भी नहीं करना पड़ता; बस mybatis-spring-boot-stater का एक jar पैकेज जोड़ना पर्याप्त है।
इस सुंदर डिज़ाइन को साकार करने की कुंजी ThreadLocal है। मोटे तौर पर सिद्धांत यह है कि stater jar पैकेज अंततः हमारे लिए एक MybatisAutoConfiguration और SPI के माध्यम से हमारी सेवा के bean कंटेनर में इंजेक्ट किया जाता है, मेंMybatisAutoConfiguration में कॉन्फ़िगर कर दिया गया हैmybatis आवश्यक SqlSessionFactory,और spring का अपना भी इंजेक्ट कियाSpringManagedTransactionFactory(जिसने लागू कियाTransactionFactory,ट्रांज़ैक्शन फ़ैक्टरी mybatis द्वारा दिया गया विस्तार तंत्र है, जिसके माध्यम से ट्रांज़ैक्शन प्रबंधन किसी अन्य को सौंपा जा सकता है)。
समूची निष्पादन प्रक्रिया यह है कि transactional एनोटेशन वाली विधि अंततः AOP प्रॉक्सी के अधीन आती है, प्रॉक्सी क्लास पूर्व-अधिसूचना में dataSource से एक connection प्राप्त करके अंततः उसे एक ThreadLocal में रखती है(वास्तव में ThreadLocal में एक map रखा जाता है, जिसकी key dataSource होती है, और value connectionHolder होती है जो connection के समतुल्य है), बाद में mybatis जब sql निष्पादित करता है तो पहले इसके माध्यम सेTransactionFactory connection प्राप्त करें,क्योंकि इस समय इंजेक्ट किया गया TransactionFactory हैSpringManagedTransactionFactory,इसलिए ThreadLocal से संबंधित dataSource का connection निकाला जाएगा।

ThreadLocal के सरल कार्य सिद्धांत का परिचय:

tapd_50394669_1660562598_27.png

ThreadLocalMap यह एक हैश तालिका है जो टकरावों को हल करने के लिए रैखिक खोज विधि का उपयोग करती है। प्रत्येक थ्रेड के लिए इसकी एक प्रति बनती है, और हर ThreadLocal ऑब्जेक्ट में एक होता हैthreadLocalHashCode हैश कुंजी की गणना के लिए उपयोग होता है।
HashMap से तुलना करें,ThreadLocalMap टकराव समाधान के लिए चेनिंग की बजाय रैखिक अन्वेषण चुनने के पीछे निम्नलिखित विचार हो सकते हैं:
1、मेमोरी संबंधी विचार: उपयोग की परिस्थितियाँ अलग होने के कारण ThreadLocalMap में डेटा की मात्रा सामान्यतः HashMap से बहुत कम होती है। कम डेटा होने पर चेनिंग विधि में अनेक अतिरिक्त पॉइंटर के लिए मेमोरी खर्च होती है
2、दक्षता, 1 के आधार पर, कम संख्या होने पर बची हुई जगह से कम लोड फ़ैक्टर वाली हैश तालिका बनाई जा सकती है, जिससे टकराव घटते हैं और औसत खोज गति बढ़ती है

ThreadLocal, थ्रेड और ThreadLocalMap के संबंध का आरेख

समस्या
A थ्रेड पूल पर आधारित कार्यों में संदर्भ के हस्तांतरण की समस्या होती है, विशेषकर Tomcat पर आधारित वेब प्रोग्रामिंग मॉडल में। चूँकि Tomcat स्वयं सर्वर और क्लाइंट के बीच अनुरोध-प्रतिक्रिया का आदान-प्रदान संभालने के लिए थ्रेड पूल का उपयोग करता है, इसलिए हर वेब अनुप्रयोग को थ्रेड संदर्भ के सही हस्तांतरण को मैन्युअल रूप से संभालना पड़ता है।
B कार्यकुशलता की समस्या, यदि अनुप्रयोग में बहुत अधिक ThreadLocal हों और हैश टेबल फैल जाए, तो खोज की गति कम हो जाएगी

समस्या A के लिए प्रचलित समाधान लगभग एक जैसे हैं, यहाँ सीधे हमारे सिस्टम का कोड दे रहा हूँ
2.png
इस समाधान में दो समस्याएँ हैं:
1、यदि आगे कोई नया ThreadLocal जोड़ा जाए जिसे सही ढंग से आगे पहुँचाना हो, तो कोड बदलकर wrap लॉजिक में उस नए ThreadLocal को जोड़ना होगा;एक भी छूट गया तो बग पैदा होगा
2、कुछ स्थितियों में कार्य उसे भेजने वाला थ्रेड स्वयं चला सकता है, जैसे:

  • जब थ्रेड पूल भर जाता है और थ्रेड पूल का RejectedExecutionHandler इस्तेमाल किया गया है CallerRunsPolicy होने पर, थ्रेड पूल में भेजा गया कार्य सीधे उसे भेजने वाले थ्रेड में चलेगा;ऐसे में अंत की सफ़ाई प्रक्रिया उस थ्रेड का कॉन्टेक्स्ट मिटा देगी

  • इसी तरह, उपयोग करके ForkJoinPool (समानांतर निष्पादन सहित Stream और CompletableFuture ,अंतर्निहित स्तर पर उपयोग होता है ForkJoinPool )वाले परिदृश्य में, विस्तारित ForkJoinTask कार्य जमा करने वाले थ्रेड में सीधे निष्पादित होगा। अंत में की जाने वाली सफ़ाई से भी जमा करने वाले थ्रेड का संदर्भ खो जाएगा

समाधान:
समस्या 1:यदि ThreadLocal क्लास ऐसा API उपलब्ध कराती जिससे पता चल सके कि अनुप्रयोग में अभी कुल कितने ThreadLocal ऑब्जेक्ट हैं,तो समस्या आसानी से हल हो जाती,लेकिन अफ़सोस कि यह सुविधा नहीं है。यदि किसी तरह करना ही हो,तो रिफ़्लेक्शन के माध्यम से thread ऑब्जेक्ट में मौजूद inheritableThreadLocals(यानी एक ThreadLocalMap)गुण,फिर हैश तालिका में घूमकर सभी entry निकाल लेने पर सभी threadlocal कुंजी-मूल्य युग्म मिल सकते हैं। एक अन्य तरीका jdk के ThreadLocal के ऊपर एक और आवरण बनाकर उसमें ऊपर बताए गए फ़ंक्शन जोड़ना है।
समस्या 2:wrap का तर्क सुधारकर इसे हल किया जा सकता है:थ्रेड का चलना शुरू होने पर पहले वर्तमान थ्रेड का संदर्भ लेकर अस्थायी रूप से सहेजें, फिर इस थ्रेड के संदर्भ को कार्य जमा करने वाले थ्रेड के संदर्भ से बदलें। थ्रेड का निष्पादन समाप्त होने से पहले संदर्भ मिटाने के बजाय अस्थायी रूप से सहेजे गए डेटा से इस थ्रेड का मूल संदर्भ बहाल करें।

transmittable-thread-local
इसके लिए तैयार समाधान पहले से मौजूद है, इसके कार्यान्वयन का सिद्धांत ऊपर दिए गए दोनों दिशाओं से काम शुरू करना है, और इस प्रक्रिया को एक नाम भी दिया गया है CRR(capture/replay/restore) मोड。
उपयोग के तीन तरीके हैं:
1 ### संशोधक Runnable और Callable

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();  
// =====================================================  
// 在父线程中设置 
context.set("value-set-in-parent"); 
Runnable task = new RunnableTask(); 
// 额外的处理,生成修饰了的对象ttl
Runnable Runnable ttlRunnable = TtlRunnable.get(task); 
executorService.submit(ttlRunnable);  
// =====================================================  
// Task中可以读取,值是"value-set-in-parent" 
String value = context.get();

2 थ्रेड पूल को डेकोरेट करना

ExecutorService executorService = ... 
// 额外的处理,生成修饰了的对象
executorService executorService = TtlExecutors.getTtlExecutorService(executorService);  
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();  
// =====================================================  
// 在父线程中设置 
context.set("value-set-in-parent");  
Runnable task = new RunnableTask(); 
Callable call = new CallableTask(); 
executorService.submit(task); 
executorService.submit(call);  
// =====================================================  
// Task或是Call中可以读取,值是"value-set-in-parent" 
String value = context.get();

3 ### उपयोग Java Agent से विशेषित करने के लिए JDK थ्रेड पूल कार्यान्वयन क्लास

// ## 1. 框架上层逻辑,后续流程框架调用业务 ## 
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>(); 
context.set("value-set-in-parent");  
// ## 2. 应用逻辑,后续流程业务调用框架下层逻辑 ## 
ExecutorService executorService = Executors.newFixedThreadPool(3);  
Runnable task = new RunnableTask(); 
Callable call = new CallableTask(); 
executorService.submit(task); 
executorService.submit(call);  
// ## 3. 框架下层逻辑 ## 
// Task或是Call中可以读取,值是"value-set-in-parent" 
String value = context.get();

java agent का उपयोग करने का लाभ यह है कि अनुप्रयोग के कोड में बदलाव नहीं करना पड़ता;नुकसान की बात करूँ तो मेरे विचार में सबसे बड़ी समस्या यह है कि jvm शुरू करते समय पैरामीटर जोड़ना भूल गए, तो गड़बड़ी हो सकती है।

स्रोत कोड का तर्क संक्षेप में देखें
3.png

4.png 

समस्या B के संबंध में, वास्तव में एप्लिकेशन विकास में आम तौर पर इस समस्या पर विचार नहीं किया जाता, क्योंकि समय दक्षता के प्रति संवेदनशीलता इतनी अधिक नहीं होती। लेकिन कुछ निचले स्तर के मिडलवेयर के विकास में प्रदर्शन अनुकूलन को अधिक महत्त्व दिया जा सकता है। उदाहरण के लिए, netty ने स्वयं कार्यान्वित करने का विकल्प चुनाFastThreadLocal jdk के ThreadLocal की जगह लेता है, साथ ही इसके अनुरूप उपलब्ध कराता हैFastThreadLocalThread,मेंFastThreadLocalThread और FastThreadLocal के साथ उपयोग करने पर,खोज की समय जटिलता हमेशा O(1) बनाए रखी जा सकती है,इस समाधान का सिद्धांत समय बचाने के लिए अतिरिक्त स्थान का उपयोग करना है,हैश तालिका में हैश टकराव से खोज की दक्षता घटने से बचाने के लिए,पर्याप्त लंबा ऐरे बनाया जाता है,प्रत्येक FastThreadLocal निर्माण के समय प्रत्येक को एक अद्वितीय ऐरे इंडेक्स दिया जाता है, मानो हर व्यक्ति की अपनी जगह हो। खोजते समय अपने index से सीधे ऐरे से मान निकाला जा सकता है।
स्रोत कोड यहाँ संक्षेप में दे रहा हूँ
5.png


टिप्पणियाँ

भेजा जा रहा है

लोकप्रिय पोस्ट

अपनी थीम चुनें

आपकी थीम पूरी साइट पर लागू होगी।