คำนำ
ปัญหาด้านความปลอดภัยของเธรดของมัลติเธรดนั้นบอบบางและไม่คาดคิดเนื่องจากลำดับการดำเนินการในมัลติเธรดนั้นไม่สามารถคาดการณ์ได้โดยไม่ต้องซิงโครไนซ์ที่เหมาะสม หลายเธรดที่เข้าถึงตัวแปรที่ใช้ร่วมกันเดียวกันนั้นมีแนวโน้มที่จะเกิดปัญหาพร้อมกันโดยเฉพาะอย่างยิ่งเมื่อหลายเธรดจำเป็นต้องเขียนตัวแปรที่ใช้ร่วมกันเพื่อให้แน่ใจว่ามีความปลอดภัยของเธรด
โดยทั่วไปผู้ใช้จำเป็นต้องทำการซิงโครไนซ์ที่เหมาะสมเมื่อเข้าถึงตัวแปรที่ใช้ร่วมกันดังแสดงในรูปด้านล่าง:
จะเห็นได้ว่าการวัดการซิงโครไนซ์โดยทั่วไปคือการล็อคซึ่งต้องการให้ผู้ใช้มีความเข้าใจบางอย่างเกี่ยวกับการล็อคซึ่งเห็นได้ชัดว่าเพิ่มภาระให้กับผู้ใช้ ดังนั้นมีวิธีเมื่อสร้างตัวแปรเมื่อแต่ละเธรดเข้าถึงได้มันจะเข้าถึงตัวแปรของเธรดของตัวเองได้หรือไม่? ในความเป็นจริง Threalocal สามารถทำสิ่งนี้ได้ โปรดทราบว่าการเกิดขึ้นของ Threadlocal ไม่ปรากฏขึ้นเพื่อแก้ปัญหาข้างต้น
Threadlocal มีให้ในแพ็คเกจ JDK มันมีตัวแปรเธรดท้องถิ่น นั่นคือถ้าคุณสร้างตัวแปร ThreadLocal แล้วแต่ละเธรดการเข้าถึงตัวแปรนี้จะมีสำเนาในท้องถิ่นของตัวแปร เมื่อหลายเธรดใช้งานตัวแปรนี้พวกเขาจะใช้ตัวแปรในหน่วยความจำท้องถิ่นของตัวเองดังนั้นจึงหลีกเลี่ยงปัญหาด้านความปลอดภัยของเธรด หลังจากสร้างตัวแปร ThreadLocal แล้ว
แต่ละเธรดจะคัดลอกตัวแปรไปยังหน่วยความจำในเครื่องดังแสดงในรูปด้านล่าง:
โอเคตอนนี้ลองนึกถึงคำถาม: หลักการใช้งานของ ThreadLocal และ ThreadLocal ถูกนำไปใช้เป็นวิธีการแยกเธรดตัวแปรภายในอย่างไร
ก่อนอื่นเราต้องดูโครงสร้างแผนภาพคลาสของ ThreadLocal ดังแสดงในรูปต่อไปนี้:
ชอบ
ดังที่เห็นได้ในแผนภาพคลาสข้างต้นมี threadlocals และมรดกที่สืบทอดมาจากคลาสเธรด ตัวแปรทั้งสองประเภทคือ ThreadLocalMap และ ThreadLocalMap เป็น HASHMAP ที่กำหนดเอง โดยค่าเริ่มต้นตัวแปรทั้งสองในแต่ละเธรดจะเป็นโมฆะ พวกเขาจะถูกสร้างขึ้นเมื่อเธรดเรียกชุด ThreadLocal หรือรับเมธอดเป็นครั้งแรก
ในความเป็นจริงตัวแปรท้องถิ่นของแต่ละเธรดจะไม่ถูกเก็บไว้ในอินสแตนซ์ ThreadLocal แต่จะถูกเก็บไว้ในตัวแปร ThreadLocals ของเธรดการเรียก กล่าวอีกนัยหนึ่งตัวแปรท้องถิ่นของประเภท ThreadLocal จะถูกเก็บไว้ในพื้นที่หน่วยความจำเธรดเฉพาะ
Threadlocal เป็นเชลล์จริง ๆ มันทำให้ค่าค่าลงในเธรดเธรดการเรียกเธรดเธรดล็อกผ่านวิธีการตั้งค่าและเก็บไว้ เมื่อเธรดการโทรเรียกใช้วิธีการรับมันจะถูกนำออกจากตัวแปร ThreadLocals ของเธรดปัจจุบัน หากเธรดการโทรไม่สิ้นสุดตัวแปรท้องถิ่นจะถูกเก็บไว้ในตัวแปร ThreadLocals ของเธรดการเรียก
ดังนั้นเมื่อคุณไม่จำเป็นต้องใช้ตัวแปรท้องถิ่นคุณสามารถลบตัวแปรโลคัลออกจากตัวแปร ThreadLocals ของเธรดปัจจุบันโดยเรียกวิธีการลบของตัวแปร ThreadLocal บางคนอาจถามว่าทำไม ThreadLocals จึงถูกออกแบบมาเป็นโครงสร้างแผนที่? เห็นได้ชัดว่าแต่ละเธรดสามารถเชื่อมโยงกับตัวแปรหลายเธรด
ต่อไปเราสามารถป้อนซอร์สโค้ดใน ThreadLocal ดังแสดงในรหัสต่อไปนี้:
ส่วนใหญ่จะดูที่ตรรกะการใช้งานของทั้งสามวิธีตั้งค่ารับและลบดังนี้:
มาดูวิธีการตั้งค่า (t var1) ก่อน
ชุดโมฆะสาธารณะ (t var1) {// (1) รับเธรดเธรดปัจจุบัน var2 = thread.currentthread (); // (2) เธรดปัจจุบันถูกใช้เป็นคีย์เพื่อค้นหาตัวแปรเธรดที่สอดคล้องกัน หากพบให้ตั้ง threadlocal.threadlocalmap var3 = this.getMap (var2); if (var3! = null) {var3.set (this, var1); } else {// (3) การโทรครั้งแรกถูกสร้างขึ้นเพื่อสร้าง hashmap ที่สอดคล้องกันสำหรับเธรดปัจจุบัน this.createMap (var2, var1); -ดังที่ได้กล่าวไว้ข้างต้นรหัส (1) ก่อนอื่นรับเธรดการโทรจากนั้นใช้เธรดปัจจุบันเป็นพารามิเตอร์เพื่อเรียกใช้เมธอด GetMap (VAR2) รหัส getMap (เธรด var2) มีดังนี้:
threadlocal.threadlocalmap getMap (เธรด var1) {return var1.threadlocals; -จะเห็นได้ว่าสิ่งที่ getMap (var2) ทำคือการรับเธรดตัวแปรของเธรดเองและตัวแปร ThreadLocal จะถูกผูกไว้กับตัวแปรสมาชิกของเธรด
หาก getMap (var2) ส่งคืนไม่ว่างเปล่าให้ตั้งค่าค่าเป็น threadlocals นั่นคือใส่ค่าตัวแปรปัจจุบันลงในตัวแปรหน่วยความจำเธรด locals ของเธรดปัจจุบัน ThreadLocals เป็นโครงสร้าง hashmap โดยที่คีย์คือการอ้างอิงของวัตถุอินสแตนซ์ threadlocal ปัจจุบันและค่าคือค่าที่ส่งผ่านวิธีการตั้งค่า
หาก getMap (var2) ส่งคืนว่างหมายความว่าวิธีการตั้งค่าเรียกว่าครั้งแรกและจากนั้นตัวแปร threadlocals ของเธรดปัจจุบันจะถูกสร้างขึ้น มาดูกันว่ามีอะไรทำใน createMap (var2, var1)?
เป็นโมฆะ createMap (เธรด var1, t var2) {var1.threadlocals = new ThreadLocal.threadLocalMap (นี่, var2); -สิ่งที่คุณเห็นได้คือการสร้างตัวแปร ThreadLocals ของเธรดปัจจุบัน
ถัดไปดูวิธีการรับ () รหัสมีดังนี้:
สาธารณะ t get () {// (4) รับเธรดเธรดปัจจุบัน var1 = tread.currentthread (); // (5) รับตัวแปร ThreadLocals ThreadLocal.threadLocalMap var2 = this.getMap (var1); // (6) ถ้า threadlocals ไม่เป็นโมฆะค่าตัวแปรท้องถิ่นที่สอดคล้องกันจะถูกส่งคืนถ้า (var2! = null) {threadlocal.threadlocalmap.entry var3 = var2.getentry (นี่); if (var3! = null) {object var4 = var3.Value; ส่งคืน var4; }} // (7) ถ้า threadlocals ว่างเปล่าตัวแปรสมาชิก ThreadLocals ของเธรดปัจจุบันจะเริ่มต้น ส่งคืนสิ่งนี้ setinitialValue (); -รหัส (4) ก่อนอื่นรับอินสแตนซ์เธรดปัจจุบัน หากตัวแปร ThreadLocals ของเธรดปัจจุบันไม่ใช่ NULL มันจะส่งคืนตัวแปรโลคัลโดยตรงของเธรดปัจจุบัน มิฉะนั้นจะดำเนินการรหัส (7) เพื่อเริ่มต้นและรหัสของ setInitialValue () มีดังนี้:
ส่วนตัว t setinitialValue () {// (8) เริ่มต้นเป็นวัตถุ null var1 = this.initialValue (); เธรด var2 = thread.currentthread (); Threadlocal.threadLocalMap var3 = this.getMap (var2); // (9) ถ้าตัวแปร ThreadLocals ของตัวแปรเธรดปัจจุบันไม่ว่างเปล่าถ้า (var3! = null) {var3.set (นี่, var1); // (10) ถ้าตัวแปร ThreadLocals ของเธรดปัจจุบันว่างเปล่า} else {this.createMap (var2, var1); } return var1; -ดังที่ได้กล่าวมาแล้วหากตัวแปร ThreadLocals ของเธรดปัจจุบันไม่ว่างเปล่าค่าตัวแปรโลคัลของเธรดปัจจุบันจะถูกตั้งค่าเป็น NULL มิฉะนั้น createMap จะถูกเรียกไปยังตัวแปร createMap ของเธรดปัจจุบัน
ต่อไปเราจะดูวิธีการเป็นโมฆะลบ () รหัสมีดังนี้:
โมฆะสาธารณะลบ () {threadlocal.threadLocalMap var1 = this.getMap (thread.currentthread ()); if (var1! = null) {var1.remove (นี่); -ดังที่ได้กล่าวไว้ข้างต้นหากตัวแปร ThreadLocals ของเธรดปัจจุบันไม่ว่างเปล่าตัวแปรโลคัลที่ระบุในอินสแตนซ์ ThreadLocal ในเธรดปัจจุบันจะถูกลบ
ถัดไปมาดูการสาธิตเฉพาะรหัสมีดังนี้:
/*** สร้างโดย CONG เมื่อปี 2018/6/3 */คลาสสาธารณะ ThreadLocalTest {// (1) การพิมพ์ฟังก์ชั่นการพิมพ์แบบคงที่การพิมพ์ (สตริง str) {//1.1 พิมพ์ค่าของตัวแปร localVariable ในหน่วยความจำท้องถิ่นของระบบเธรดปัจจุบัน. //1.2 ล้างตัวแปร localVariable ในหน่วยความจำท้องถิ่นของเธรดปัจจุบัน //localvariable.remove (); } // (2) สร้าง ThreadLocal ตัวแปร Static Static threadLocal <String> localVariable = new ThreadLocal <> (); โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// (3) สร้างเธรดหนึ่งเธรดเธรด = เธรดใหม่ (ใหม่ runnable () {โมฆะสาธารณะเรียกใช้ () {//3.1 ตั้งค่าของตัวแปรท้องถิ่น localVariable ในเธรด localvariable System.out.println ("ผลลัพธ์หลังจากลบตัวแปรท้องถิ่นของเธรด 1" + ":" + localVariable.get ()); // (4) สร้างเธรดสองเธรด Threadtwo = ใหม่เธรด (ใหม่ runnable () {public void run () {//4.1 ตั้งค่าของตัวแปรท้องถิ่น localVariable ในเธรดหนึ่ง localVariable.set ("ตัวแปรท้องถิ่นของเธรด 2"); //4.2 การเรียกใช้ฟังก์ชันการพิมพ์ ตัวแปรของเธรด 2 " +": " + localVariable.get ());}}); // (5) เริ่มเธรดเธรด start (); Threadtwo.start (); -รหัส (2) สร้างตัวแปร ThreadLocal;
รหัส (3) และ (4) สร้างเธรด 1 และ 2 ตามลำดับ
รหัส (5) เริ่มสองเธรด;
รหัส 3.1 ในเธรด 1 ตั้งค่าของ localVariable ผ่านวิธีการตั้งค่า การตั้งค่านี้เป็นสำเนาในหน่วยความจำท้องถิ่นของเธรด 1 สำเนานี้ไม่สามารถเข้าถึงได้โดยเธรด 2 จากนั้นรหัส 3.2 เรียกใช้ฟังก์ชั่นการพิมพ์และรหัส 1.1 ได้รับค่าของ localVariable ในหน่วยความจำท้องถิ่นของเธรดปัจจุบัน (เธรด 1) ผ่านฟังก์ชั่น Get;
เธรด 2 ดำเนินการคล้ายกับเธรด 1
ผลการดำเนินการมีดังนี้:
ที่นี่เราต้องให้ความสนใจกับปัญหาการรั่วไหลของหน่วยความจำของ Threadlocal
แต่ละเธรดมีตัวแปรสมาชิกชื่อ ThreadLocals ภายใน ประเภทตัวแปรคือ HashMap คีย์คือการอ้างอิงถึงตัวแปร ThreadLocal ที่เรากำหนดไว้และค่าคือค่าเมื่อเราตั้งค่า ตัวแปรโลคัลของแต่ละเธรดจะถูกเก็บไว้ในเธรดหน่วยความจำของเธรด หากเธรดปัจจุบันไม่หายไปตัวแปรท้องถิ่นเหล่านี้จะถูกเก็บไว้จนกระทั่ง
ดังนั้นอาจทำให้เกิดการรั่วไหลของหน่วยความจำดังนั้นหลังจากใช้แล้วอย่าลืมเรียกวิธีการลบของ ThreadLocal เพื่อลบตัวแปรท้องถิ่นใน ThreadLocals ของเธรดที่เกี่ยวข้อง
หลังจากเปิดความคิดเห็นในรหัส 1.2 ให้รันอีกครั้งและผลการรันเป็นดังนี้:
เราเคยคิดเกี่ยวกับคำถามเช่นนี้: เราได้รับค่าของตัวแปร ThreadLocal ที่ตั้งไว้ในเธรดหลักในเธรดลูกหรือไม่?
ที่นี่เราสามารถบอกคุณได้ว่าค่าของตัวแปร ThreadLocal ที่ตั้งไว้ในเธรดพาเรนต์ไม่สามารถรับได้ในเธรดลูก ดังนั้นมีวิธีที่จะทำให้เธรดลูกเพื่อเข้าถึงค่าในเธรดพาเรนต์หรือไม่? มรดกที่ได้รับการตรวจสอบเข้ามาในอนาคต MandleItableThreadLocal สืบทอดมาจาก ThreadLocal และให้คุณสมบัติที่เด็กสามารถเข้าถึงตัวแปรท้องถิ่นที่ตั้งไว้ในเธรดพาเรนต์
ก่อนอื่นให้ไปที่ซอร์สโค้ดของคลาส MandleItableThreadLocal เพื่ออ่านดังนี้:
คลาสสาธารณะ MandleItableThreadLocal <T> ขยาย ThreadLocal <t> {Public MandleItableThreadLocal () {} // (1) การป้องกัน t childValue (t var1) {return var1; } // (2) ThreadLocalMap getMap (เธรด var1) {return var1.inheritableThreatHreocals; } // (3) โมฆะ createMap (เธรด var1, t var2) {var1.inheritableTheThreadLocals = ใหม่ ThreadLocalMap (this, var2); -คุณจะเห็นได้ว่ามรดกที่สืบทอดมาจากการสืบทอด threadlocal และเขียนใหม่สามวิธี รหัสด้านบนถูกทำเครื่องหมายไว้ รหัส (3) จะเห็นได้ว่า MandleItableThreadLocal เขียนวิธี createMap ใหม่ดังนั้นจะเห็นได้ว่าเมื่อวิธีการตั้งค่าถูกเรียกเป็นครั้งแรกอินสแตนซ์ของตัวแปรที่สืบทอดมาจากตัวแปรของเธรดปัจจุบันถูกสร้างขึ้นแทนที่จะเป็นเธรด
รหัส (2) คุณสามารถรู้ได้ว่าเมื่อมีการเรียกเมธอดเพื่อให้ได้ตัวแปรแผนที่ภายในของเธรดปัจจุบันจะได้รับ MandleItableThreadLocals ไม่ใช่ ThreadLocals
จุดสำคัญคือที่นี่เมื่อมีการดำเนินการรหัสใหม่ (1) และวิธีการใช้งานที่เธรดลูกสามารถเข้าถึงตัวแปรท้องถิ่นของเธรดพาเรนต์ เริ่มต้นด้วยรหัสที่สร้างโดยเธรดตัวสร้างเริ่มต้นของเธรดและตัวสร้างของคลาส thread.java มีดังนี้:
/*** สร้างโดย CONG เมื่อปี 2018/6/3 */ เธรดสาธารณะ (เป้าหมายที่เรียกใช้ได้) {init (null, target, "thread-" + nextthreadnum (), 0); } private void init (threadgroup g, เป้าหมายที่เรียกใช้, ชื่อสตริง, stacksize ยาว, accessControlContext acc) {// ... // (4) รับเธรดเธรดพรัลต์ปัจจุบัน = currentThread (); // ... // (5) ถ้าตัวแปรที่สืบทอดมาจากตัวแปรของเธรดพาเรนต์ไม่ได้เป็นโมฆะถ้า (parent.inherItableThreadLocals! = null) // (6) ตั้งค่าตัวแปรที่สืบทอดมาได้ this.stackSize = stackSize; tid = nextthreadid (); -เมื่อสร้างเธรดวิธีการเริ่มต้นจะถูกเรียกในตัวสร้าง ดังที่ได้กล่าวไว้ก่อนหน้านี้คลาส MandleItableThreadLocal GET และวิธีการตั้งค่าใช้งานตัวแปรที่สืบทอดมาจากตัวแปรดังนั้นตัวแปร MandleItableThreadLocal ที่นี่จึงไม่เป็นโมฆะดังนั้นรหัส (6) จะถูกดำเนินการ มาดูซอร์สโค้ดของวิธี CreateInheritedMap ดังต่อไปนี้:
Static ThreadLocalMap createInheritedMap (ThreadLocalMap parentmap) {ส่งคืน ThreadLocalMap ใหม่ (ParentMap); -คุณจะเห็นได้ว่า CreateInheritedMap ใช้ตัวแปร MandleItableThreadLocals ของเธรดพาเรนต์เป็นตัวสร้างเพื่อสร้างตัวแปร ThreadLocalMap ใหม่จากนั้นกำหนดให้กับตัวแปร ModyIntableThreadLocals ของเธรดเด็ก จากนั้นป้อนตัวสร้างของ ThreadLocalMap ซอร์สโค้ดมีดังนี้:
Private ThreadLocalMap (ThreadLocalMap ParentMap) {entry [] parentTable = parentMap.Table; int len = parentTable.length; Setthreshold (Len); ตาราง = รายการใหม่ [len]; สำหรับ (int j = 0; j <len; j ++) {entry e = parenttable [j]; if (e! = null) {@suppresswarnings ("ไม่ได้ตรวจสอบ") ThreadLocal <jobch> key = (threadLocal <Ojrop>) e.get (); if (key! = null) {// (7) เรียกค่าวัตถุวิธีการ overridden = key.childValue (e.value); // return e.value entry c = รายการใหม่ (คีย์, ค่า); int h = key.threadLocalHashCode & (len - 1); ในขณะที่ (ตาราง [h]! = null) h = nextindex (h, len); ตาราง [h] = c; ขนาด ++; -สิ่งที่รหัสข้างต้นทำคือคัดลอกค่าของตัวแปร member member ของเธรดที่สืบทอดของเธรดไปยังวัตถุ ThreadLocalMap ใหม่และโค้ด (1) เขียนใหม่โดยรหัส (7) ModeTableTheThreadLocal คลาสก็เข้ามาในมุมมอง
โดยทั่วไป: คลาส MandleItableThreadLocal จะเขียนโค้ด (2) และ (3) และบันทึกตัวแปรท้องถิ่นลงในตัวแปร MandleItableThreadLocals ของเธรดเฉพาะ เมื่อเธรดตั้งค่าตัวแปรผ่านวิธีการตั้งค่าหรือรับของอินสแตนซ์ที่ได้รับการตรวจสอบมรดกมันจะสร้างตัวแปรที่ได้รับการตรวจสอบมรดกของเธรดปัจจุบัน เมื่อเธรดหลักสร้างเธรดลูก
ตัวสร้างจะคัดลอกตัวแปรโลคัลในตัวแปร MandleItableThreadLocals ในเธรดพาเรนต์และคัดลอกลงในตัวแปร MandleItableThreadLocals ของเธรดลูก
หลังจากเข้าใจหลักการแล้วลองมาเป็นตัวอย่างเพื่อตรวจสอบสิ่งที่เรารู้ข้างต้นดังนี้:
แพ็คเกจ com.hjc;/*** สร้างโดย cong เมื่อ 2018/6/3 */คลาสสาธารณะ MandleItableThreadLocalTest {// (1) สร้างเธรดตัวแปรสาธารณะคงที่ threadLocal <String> threadLocal = ใหม่ ThreadLocal <String> (); โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// (2) ตั้งเธรดเธรดตัวแปร ThreadLocal.set ("Hello Java"); // (3) เริ่มเธรดเธรดลูก = เธรดใหม่ (ใหม่ runnable () {public void run () {// (4) ค่าของเธรดลูกเอาต์พุตตัวแปรเธรด System.out.println ("subthread:" + threadlocal.get ());}}); thread.start (); // (5) เธรดหลักส่งออกค่าตัวแปรเธรด System.out.println ("เธรดหลัก:" + threadlocal.get ()); -ผลการดำเนินการมีดังนี้:
กล่าวคือหลังจากตั้งค่าตัวแปร ThreadLocal เดียวกันในเธรดหลักแล้วจะไม่สามารถรับได้ในเธรดลูก ตามการแนะนำในส่วนก่อนหน้านี้ควรเป็นปรากฏการณ์ปกติเนื่องจากเธรดปัจจุบันเป็นเธรดลูกเมื่อเธรดลูกเรียกเมธอด GET และวิธีการตั้งค่าจะถูกเรียกให้ตั้งค่าตัวแปรเธรดเป็นเธรดหลัก ทั้งสองเป็นเธรดที่แตกต่างกันและโดยธรรมชาติแล้วเธรดลูกจะกลับมาเป็นโมฆะเมื่อเข้าถึง
ดังนั้นมีวิธีที่จะทำให้เธรดลูกเพื่อเข้าถึงค่าในเธรดพาเรนต์หรือไม่? คำตอบคือใช่ดังนั้นใช้การวิเคราะห์ที่สืบทอดมาจากหลักการข้างต้นของเรา
แก้ไขรหัส (1) ของตัวอย่างด้านบนเป็น:
// (1) สร้างเธรดตัวแปรสาธารณะคงที่ threadLocal <String> threadLocal = new MandleItableThreadLocal <String> ();
ผลการดำเนินการมีดังนี้:
จะเห็นได้ว่าตอนนี้ค่าตัวแปรเธรดสามารถรับได้ตามปกติจากเธรดลูก ดังนั้นเด็กจำเป็นต้องได้รับตัวแปร Threadlocal ภายใต้สถานการณ์ใดจากเธรดหลัก?
มีสถานการณ์ค่อนข้างมากเช่นตัวแปร ThreadLocal ที่เก็บข้อมูลการเข้าสู่ระบบของผู้ใช้ เป็นไปได้มากที่ subthreads จำเป็นต้องใช้ข้อมูลการเข้าสู่ระบบของผู้ใช้ ตัวอย่างเช่นมิดเดิลแวร์บางตัวต้องใช้ ID การติดตามแบบครบวงจรเพื่อบันทึกลิงก์การโทรทั้งหมด
การใช้ Threadlocal ในขอบเขตการร้องขอสปริงขอบเขตถั่ว
เรารู้ว่าเมื่อกำหนดค่าถั่วใน XML ในฤดูใบไม้ผลิคุณสามารถระบุแอตทริบิวต์ขอบเขตเพื่อกำหนดค่าขอบเขตของถั่วให้เป็นซิงเกิล, ต้นแบบ, การร้องขอ, เซสชัน ฯลฯ หากคุณต้องการให้ถั่วในภาชนะฤดูใบไม้ผลิของคุณมีขอบเขตของเว็บ
นอกเหนือจากแอตทริบิวต์ขอบเขตที่เกี่ยวข้องที่จำเป็นในการกำหนดค่าระดับถั่วแล้วต้องกำหนดค่าใน web.xml ดังต่อไปนี้:
<Sistener> <Sistener-Lass> org.springframework.web.context.request.requestcontextListener </listener-class> </listener>
ที่นี่เราดูสองวิธีของ requestcontextListener:
โมฆะสาธารณะขอให้มีการกำหนดขั้นตอน (ServletRequestEvent RequestEvent)
และ
Public Void RequestDestroyed (ServletRequestEvent RequestEvent)
เมื่อมีการร้องขอเว็บวิธีการร้องขอจะถูกดำเนินการ:
โมฆะสาธารณะขอให้มีการกำหนด (servletRequestEvent RequestEvent) {..... omit httpservletRequest Request = (httpservletRequest) RequestEvent.getServletRequest (); ServletRequestattributes แอตทริบิวต์ = ใหม่ servletRequestatTributes (คำขอ); request.setAttribute (request_attributes_attribute, แอตทริบิวต์); localecontextholder.setLocale (request.getLocale ()); // ตั้งค่าแอตทริบิวต์เป็นตัวแปร threadlocal requestcontextholder.setRequestattributes (แอตทริบิวต์); } โมฆะคงที่สาธารณะ setRequestattributes (requestattributes แอตทริบิวต์) {setRequestatTributes (แอตทริบิวต์, เท็จ); } โมฆะคงที่สาธารณะ setRequestatTributes (requestattributes แอตทริบิวต์, บูลีนที่สืบทอดได้) {ถ้า (แอตทริบิวต์ == null) {resetrequestattributes (); } else {// ค่าเริ่มต้นที่สืบทอด = FALSE ถ้า (สืบทอด) {MandleItableRequestatTributeSholder.set (แอตทริบิวต์); requestattributesholder.remove (); } else {requestattributeSholder.set (แอตทริบิวต์); มรดกที่ได้รับการรับรองความเป็นมรดก (); -คุณสามารถดูซอร์สโค้ดด้านบน เนื่องจากค่าเริ่มต้นที่สืบทอดเป็นเท็จค่าแอตทริบิวต์ของเราจึงถูกวางไว้ใน requestattributeShoder และคำจำกัดความของมันคือ:
Private Static Final ThreadLocal <EquestAttributes> quorkattributeSholder = new NamedThreadLocal <EquestAttributes> ("คำขอแอตทริบิวต์"); Private Static Final ThreadLocal <EquestAtTributes> MandleItableRequestatTributesholder = new NamedInherItableThreadLocal <EquestAtTributes> ("บริบทการร้องขอ");ในหมู่พวกเขา NamedThreadLocal <T> ขยาย ThreadLocal <T> ดังนั้นจึงไม่สามารถสืบทอดได้
ในหมู่พวกเขา NamedThreadLocal <T> ขยาย ThreadLocal <T> ดังนั้นจึงไม่สามารถสืบทอดได้
NameInherItableThreadLocal <T> ขยาย MandleItableThreatHreatLocal <t> ดังนั้นจึงมีการสืบทอดดังนั้นค่าแอตทริบิวต์ที่วางไว้ใน requestContextholder โดยค่าเริ่มต้นไม่สามารถรับได้ในเธรดลูก
เมื่อคำขอสิ้นสุดลงวิธีการที่ถูกร้องขอจะถูกเรียกและซอร์สโค้ดมีดังนี้:
โมฆะสาธารณะขอ Destroyed (ServletRequestEvent RequestEvent) {ServletRequestatTributes แอตทริบิวต์ = (servletRequestattributes) requestEvent.getServletRequest (). getAttribute (request_attributes_attribute); ServletRequestattributes Threadattributes = (servletRequestattributes) requestcontextholder.getRequestattributes (); if (threadattributes! = null) {// เรามีแนวโน้มที่จะล้างเธรดของเธรดปัจจุบันในเธรดการร้องขอเริ่มต้นถ้า (แอตทริบิวต์ == null) {attributes = threadattributes; } // เมื่อคำขอสิ้นสุดให้ล้างตัวแปรเธรดของเธรดปัจจุบัน localecontextholder.resetlocalecontext (); requestcontextholder.resetrequestattributes (); } if (แอตทริบิวต์! = null) {attributes.requestcompleted (); -ถัดไปลองดูที่ตรรกะของการร้องขอเว็บการโทรจากแผนภูมิเวลา:
กล่าวคือทุกครั้งที่มีการเริ่มต้นคำขอเว็บก่อนที่จะประมวลผลบริบท (แอปพลิเคชันเฉพาะ) ใน TOMCAT คุณสมบัติของ RequestContextholder จะถูกตั้งค่าหลังจากการจับคู่โฮสต์เพื่อให้คำขอ requestattributesholder ไม่ว่างเปล่าและจะถูกล้างในตอนท้ายของคำขอ
ดังนั้นโดยค่าเริ่มต้นเธรดลูกแอตทริบิวต์ที่วางไว้ใน requestcontextholder ไม่สามารถเข้าถึงได้และถั่วในขอบเขตการร้องขอของสปริงจะถูกนำไปใช้โดยใช้ ThreadLocal
ถัดไปจะดำเนินการตามคำขอจำลองการจำลองรหัสมีดังนี้:
การกำหนดค่า web.xml มีดังนี้:
เนื่องจากเป็นขอบเขตการร้องขอจึงต้องเป็นโครงการเว็บและต้องมีการกำหนดค่าให้กับ web.xml
<Sistener> <Sistener-Lass> org.springframework.web.context.request.requestcontextListener </listener-class> </listener>
จากนั้นฉีดถั่วขอบเขตคำขอลงในคอนเทนเนอร์ IOC รหัสมีดังนี้:
<bean id = "requestBean" scope = "คำขอ"> <property name = "name" value = "hjc" /> <aop: scoped-proxy /> </ebean>
รหัสทดสอบมีดังนี้:
@webresource ("/testservice") คลาสสาธารณะ testrpc {@autoWired Private RequestBean RequestInfo; @ResourceMapping ("ทดสอบ") การทดสอบการกระทำสาธารณะ (บริบท errorContext) {actionResult result = new ActionResult (); pvginfo.setName ("HJC"); ชื่อสตริง = requestInfo.getName (); result.setValue (ชื่อ); ผลการกลับมา; -ดังที่ได้กล่าวไว้ข้างต้นกำหนดค่าก่อนกำหนด requestcontextListener ลงใน web.xml จากนั้นฉีดอินสแตนซ์ requestbean ลงในคอนเทนเนอร์ IOC ด้วยขอบเขตการร้องขอ ในที่สุดอินสแตนซ์ของ RequestBean จะถูกฉีดลงใน TESTRPC วิธีการทดสอบวิธีแรกเรียกเมธอด requestInfo setName เพื่อตั้งค่าแอตทริบิวต์ชื่อจากนั้นรับแอตทริบิวต์ชื่อและส่งคืน
ที่นี่หากวัตถุ RequestInfo เป็น Singleton หลังจากหลายเธรดเรียกวิธีการทดสอบในเวลาเดียวกันแต่ละเธรดจะเป็นการดำเนินการที่ตั้งค่าไว้ การดำเนินการนี้ไม่ใช่อะตอมและจะทำให้เกิดปัญหาด้านความปลอดภัยของด้าย ขอบเขตที่ประกาศไว้ที่นี่คือระดับการร้องขอและแต่ละเธรดมีตัวแปรโลคัลที่มี requestInfo
แผนภูมิเวลาของคำขอวิธีการตัวอย่างข้างต้นมีดังนี้:
เราต้องมุ่งเน้นไปที่สิ่งที่เกิดขึ้นเมื่อโทรทดสอบ:
ในความเป็นจริงคำขอที่สร้างขึ้นก่อนหน้านี้คือหลังจาก proxyed โดย cglib (หากคุณสนใจคุณสามารถศึกษา scopedproxyfactorybean และประเภทอื่น ๆ ) ดังนั้นเมื่อคุณโทร setname หรือ getName คุณจะถูกดักจับโดย DynamicAdvisedInterceptor ในที่สุด Interceptor จะเรียกวิธี GET ของ RequestScope เพื่อรับตัวแปรท้องถิ่นที่จัดขึ้นโดยเธรดปัจจุบัน
กุญแจอยู่ที่นี่ เราจำเป็นต้องดูที่ซอร์สโค้ดของวิธีการร้องขอ Get ได้ดังนี้:
วัตถุสาธารณะได้รับ (ชื่อสตริง, ObjectFactory ObjectFactory) {requestattributes attributes = requestcontextholder.currentrequestattributes (); // (1) Object popeDoBject = attributes.getAttribute (ชื่อ, getScope ()); if (scopedObject == null) {scopedObject = objectFactory.getObject (); // (2) แอตทริบิวต์ SetAttribute (ชื่อ, scopedObject, getScope ()); // (3)} return spopedObject; -จะเห็นได้ว่าเมื่อมีการร้องขอการร้องขอ requestattributeSholder จะถูกตั้งค่าโดยการเรียกร้องขอ requestcontextlistener.requestinitialized ใน requestcontextListener.setRequestattRibutess
จากนั้นหลังจากคำขอจะถูกส่งไปยังวิธีการทดสอบของ TestRPC ครั้งแรกที่วิธีการตั้งชื่อจะถูกเรียกในวิธีการทดสอบวิธีการร้องขอ SEQUESCOPE.GET () จะถูกเรียกในที่สุด รหัสในเมธอด Get (1) ได้รับค่าของชุดแอตทริบิวต์ที่บันทึกโดยตัวแปรเธรด local requestattributeSholder ที่ตั้งค่าผ่านการร้องขอ thontextListener.requestinitialized
จากนั้นตรวจสอบว่ามีแอตทริบิวต์ชื่อ requestinfo ในชุดแอตทริบิวต์หรือไม่ เนื่องจากมันเป็นสายแรกจึงไม่มีอยู่ดังนั้นรหัสจะถูกเรียกใช้งาน (2) และปล่อยให้สปริงสร้างวัตถุ requestinfo จากนั้นตั้งค่าเป็นแอตทริบิวต์ชุดแอตทริบิวต์นั่นคือมันจะถูกบันทึกไว้ในหน่วยความจำท้องถิ่นของเธรดคำขอปัจจุบัน จากนั้นส่งคืนวัตถุที่สร้างขึ้นและเรียกใช้ชื่อ setName ของวัตถุที่สร้างขึ้น
ในที่สุดวิธี getName จะถูกเรียกในวิธีการทดสอบและวิธีการ requestscope.get () จะถูกเรียก รหัสในวิธีการรับ (1) รับเธรดตัวแปรโลคัลตัวแปร requestattributes ที่ตั้งค่าผ่าน requestcontextListener.requestinitialized จากนั้นดูว่ามีแอตทริบิวต์ชื่อ requestInfo ในชุดแอตทริบิวต์หรือไม่
เนื่องจากถั่วที่มีชื่อ requestInfo ถูกตั้งค่าเป็นตัวแปร ThreadLocal เมื่อมีการเรียกใช้ SetName เป็นครั้งแรกและเธรดเดียวกันจะถูกเรียกใช้ SetName และ GetName วัตถุ RequestInfo ที่สร้างขึ้นเมื่อเรียก SetName โดยตรงที่นี่
จนถึงตอนนี้เราได้เข้าใจหลักการดำเนินการของ ThreadLocal และชี้ให้เห็นว่า ThreadLocal ไม่สนับสนุนการสืบทอด จากนั้นเราจะอธิบายทันทีว่ามรดกที่สืบทอดมาได้อย่างต่อเนื่องจะชดเชยคุณลักษณะที่ ThreadLocal ไม่รองรับการสืบทอดได้อย่างไร ในที่สุดเราแนะนำวิธีการใช้ ThreadLocal ในกรอบฤดูใบไม้ผลิสั้น ๆ เพื่อใช้ถั่วของขอบเขต reqeust
สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่าเนื้อหาของบทความนี้จะมีค่าอ้างอิงบางอย่างสำหรับการศึกษาหรือที่ทำงานของทุกคน หากคุณมีคำถามใด ๆ คุณสามารถฝากข้อความไว้เพื่อสื่อสาร ขอบคุณสำหรับการสนับสนุน Wulin.com