คำหลักที่ซิงโครไนซ์
เมื่อใช้คำหลักภาษา Java เพื่อแก้ไขวิธีการหรือบล็อกโค้ดก็สามารถตรวจสอบให้แน่ใจว่าส่วนใหญ่หนึ่งเธรดจะดำเนินการรหัสในเวลาเดียวกัน
เมื่อสองเธรดที่เกิดขึ้นพร้อมกันเข้าถึงบล็อกรหัสที่ซิงโครไนซ์นี้ (นี้) ในวัตถุวัตถุเดียวกันสามารถดำเนินการได้เพียงหนึ่งเธรดภายในหนึ่งครั้ง เธรดอื่นจะต้องรอให้เธรดปัจจุบันเรียกใช้งานบล็อกรหัสนี้ก่อนที่จะสามารถเรียกใช้งานบล็อกรหัส
อย่างไรก็ตามเมื่อเธรดหนึ่งเข้าถึงบล็อกรหัสซิงโครไนซ์ซิงโครไนซ์หนึ่งรายการของวัตถุของวัตถุเธรดอื่นยังสามารถเข้าถึงบล็อกรหัสการซิงโครไนซ์ที่ไม่ซิงโครไนซ์ (นี่) ในวัตถุนั้น
เป็นสิ่งสำคัญอย่างยิ่งที่เมื่อเธรดเข้าถึงบล็อกรหัสซิงโครไนซ์ซิงโครไนซ์ (นี้) ของวัตถุเธรดอื่น ๆ จะบล็อกการเข้าถึงไปยังบล็อกรหัสซิงโครไนซ์อื่น ๆ ทั้งหมด (นี้) ในวัตถุ
ตัวอย่างที่สามยังใช้กับบล็อกรหัสซิงโครนัสอื่น ๆ นั่นคือเมื่อเธรดเข้าถึงบล็อกรหัสซิงโครไนซ์ซิงโครไนซ์ (นี้) ของวัตถุจะได้รับการล็อควัตถุของวัตถุนี้ เป็นผลให้เธรดอื่น ๆ เข้าถึงส่วนรหัสแบบซิงโครนัสทั้งหมดของวัตถุวัตถุถูกบล็อกชั่วคราว
กฎข้างต้นยังใช้กับล็อควัตถุอื่น ๆ
ตัวอย่างรหัส
แพ็คเกจ Test160118; Public Class Testsynchronized {โมฆะสาธารณะคงที่หลัก (String [] args) {sy sy = ใหม่ sy (0); SY SY2 = ใหม่ SY (1); Sy.Start (); sy2.start (); }} คลาส SY ขยายเธรด {FLAG INT ส่วนตัว; วัตถุคงที่ x1 = วัตถุใหม่ (); วัตถุคงที่ x2 = วัตถุใหม่ (); SY สาธารณะ (Flag int) {this.flag = Flag; } @Override โมฆะสาธารณะ Run () {System.out.println (Flag); ลอง {if (flag == 0) {ซิงโครไนซ์ (x1) {system.out.println (แฟล็ก+"ล็อค x1"); Thread.sleep (1,000); ซิงโครไนซ์ (x2) {system.out.println (แฟล็ก+"ล็อค x2"); } System.out.println (Flag+"Release X1 และ X2"); }} if (flag == 1) {ซิงโครไนซ์ (x2) {system.out.println (แฟล็ก+"ล็อค x2"); Thread.sleep (1,000); ซิงโครไนซ์ (x1) {system.out.println (แฟล็ก+"ล็อค x1"); } System.out.println (Flag+"Release X1 และ X2"); }}} catch (interruptedException e) {e.printStackTrace (); -
ThreadLocal Lock-Free Thread ENCLOSING หลักการการนำไปใช้งาน
Threadlocal ทำอะไรได้บ้าง?
ประโยคนี้ยากที่จะพูด ลองมาดูความยากลำบากบางอย่างที่พบในโครงการจริง: เมื่อคุณเรียกใช้วิธีการบางอย่างตามพารามิเตอร์บางอย่างในโครงการจากนั้นวิธีการเรียกวิธีการแล้วเรียกใช้วิธีการข้ามวัตถุวิธีเหล่านี้อาจใช้พารามิเตอร์ที่คล้ายกันบางอย่าง ในเวลานี้พารามิเตอร์ทั้งหมดจะต้องส่งผ่านไปยัง B. และอื่น ๆ หากมีหลายวิธีที่เรียกว่าพารามิเตอร์จะมากขึ้นเรื่อย ๆ นอกจากนี้เมื่อโปรแกรมจำเป็นต้องเพิ่มพารามิเตอร์คุณต้องเพิ่มพารามิเตอร์ลงในวิธีที่เกี่ยวข้องทีละหนึ่ง ใช่มันลำบากมาก ฉันเชื่อว่าคุณได้พบมัน นี่เป็นวิธีการประมวลผลทั่วไปสำหรับวัตถุที่มุ่งเน้นในภาษา C อย่างไรก็ตามวิธีการประมวลผลอย่างง่ายของเราคือการห่อลงในวัตถุและส่งผ่านปัญหานี้สามารถแก้ไขได้โดยการเพิ่มแอตทริบิวต์ของวัตถุ อย่างไรก็ตามวัตถุมักจะมีความหมายดังนั้นบางครั้งบรรจุภัณฑ์วัตถุที่เรียบง่ายเพิ่มคุณลักษณะที่ไม่เกี่ยวข้องบางอย่างเพื่อให้คำจำกัดความของชั้นเรียนของเราแปลกมากดังนั้นในกรณีเหล่านี้เมื่อเราจัดโครงสร้างโปรแกรมที่ซับซ้อนเช่นนี้เราใช้ขอบเขตบางอย่างคล้ายกับขอบเขตเพื่อจัดการ ชื่อและการใช้งานเป็นเรื่องธรรมดามากขึ้น คล้ายกับเว็บแอปพลิเคชันจะมีขอบเขตที่บริบทเซสชันการร้องขอและระดับหน้า Threadlocal ยังสามารถแก้ปัญหาประเภทนี้ได้ แต่ก็ไม่เหมาะที่จะแก้ปัญหาประเภทนี้ เมื่อเผชิญกับปัญหาเหล่านี้มันมักจะไม่ผ่านพวกเขาในระยะแรกของขอบเขตและวัตถุและเชื่อว่าจะไม่เพิ่มพารามิเตอร์ เมื่อเพิ่มพารามิเตอร์ฉันพบว่ามีหลายสถานที่ที่จะเปลี่ยนแปลง เพื่อไม่ให้ทำลายโครงสร้างของรหัสเป็นไปได้ว่ามีพารามิเตอร์มากเกินไปซึ่งได้ลดความสามารถในการอ่านของรหัสวิธี Threadlocal ถูกเพิ่มเข้ามาเพื่อจัดการ ตัวอย่างเช่นเมื่อวิธีหนึ่งเรียกวิธีการอื่นจะมีการส่งพารามิเตอร์ 8 พารามิเตอร์และหนึ่งในพารามิเตอร์จะถูกส่งผ่านโดยการเรียกเลเยอร์เมธอด nth โดยเลเยอร์ ในเวลานี้วิธีสุดท้ายต้องเพิ่มพารามิเตอร์หนึ่งพารามิเตอร์ เป็นเรื่องธรรมดาสำหรับวิธีแรกที่จะกลายเป็นพารามิเตอร์ 9 ตัว แต่ในเวลานี้วิธีการที่เกี่ยวข้องจะเกี่ยวข้องทำให้รหัสป่อง
threadlocal ที่กล่าวถึงข้างต้นเป็นจุดประสงค์ในการซ่อมแซมปัญหาการสูญเสียแกะ แต่มันไม่ได้เป็นวิธีที่แนะนำโดยเฉพาะอย่างยิ่งในการใช้งาน นอกจากนี้ยังมีวิธีการที่คล้ายกันในการใช้นั่นคือมีการโทรแบบไดนามิกจำนวนมากในระดับเฟรมเวิร์กและโปรโตคอลบางอย่างจำเป็นต้องพบในระหว่างกระบวนการโทร แม้ว่าเราจะพยายามเป็นสากล แต่พารามิเตอร์เพิ่มเติมจำนวนมากไม่ใช่เรื่องง่ายที่จะพิจารณาเมื่อกำหนดโปรโตคอลและเวอร์ชันจะได้รับการอัพเกรดได้ตลอดเวลา อย่างไรก็ตามเมื่อมีการขยายเฟรมเวิร์กอินเทอร์เฟซก็เป็นสากลและย้อนหลังได้ เราต้องการเนื้อหาเพิ่มเติมเพื่อให้สะดวกและเรียบง่าย
พูดง่ายๆคือ Threadlocal เปลี่ยนส่วนขยายระบบที่ซับซ้อนให้เป็นคำจำกัดความง่าย ๆ ทำให้ชิ้นส่วนที่เกี่ยวข้องกับพารามิเตอร์ที่เกี่ยวข้องง่ายมาก นี่คือตัวอย่างของเรา:
ในตัวจัดการธุรกรรมของฤดูใบไม้ผลิการเชื่อมต่อที่ได้จากแหล่งข้อมูลจะถูกวางไว้ใน ThreadLocal หลังจากดำเนินการโปรแกรมแล้วการเชื่อมต่อจะได้รับจาก ThreadLocal จากนั้นจะทำการย้อนกลับและย้อนกลับ ในการใช้งานมีความจำเป็นเพื่อให้แน่ใจว่าการเชื่อมต่อที่ได้รับจากโปรแกรมผ่านแหล่งข้อมูลนั้นได้มาจากฤดูใบไม้ผลิ ทำไมการดำเนินการดังกล่าว? เนื่องจากรหัสธุรกิจถูกกำหนดโดยแอปพลิเคชันอย่างสมบูรณ์และกรอบการทำงานไม่สามารถกำหนดรหัสธุรกิจได้มิฉะนั้นเฟรมเวิร์กจะสูญเสียผลประโยชน์ของการไม่อนุญาตให้รหัสธุรกิจจัดการการเชื่อมต่อ หลังจากตัดรหัสธุรกิจแล้วฤดูใบไม้ผลิจะไม่ผ่านการเชื่อมต่อกับพื้นที่รหัสธุรกิจ จะต้องได้รับการบันทึกในสถานที่ เมื่อเลเยอร์พื้นฐานผ่าน Ibatis และ Spring เมื่อเฟรมเวิร์กเช่น JDBC ได้รับการเชื่อมต่อของแหล่งข้อมูลเดียวกันมันจะเรียกให้ได้รับตามกฎที่ตกลงกันไว้ในฤดูใบไม้ผลิ เนื่องจากกระบวนการดำเนินการถูกประมวลผลในเธรดเดียวกันการเชื่อมต่อเดียวกันจึงได้รับเพื่อให้แน่ใจว่าการเชื่อมต่อที่ใช้นั้นเหมือนกันในระหว่างการกระทำการย้อนกลับและการดำเนินธุรกิจเนื่องจากมีเพียง connecton เดียวกันเท่านั้นที่สามารถรับประกันการทำธุรกรรมมิฉะนั้นจะไม่รองรับฐานข้อมูล
ในความเป็นจริงในแอปพลิเคชันการเขียนโปรแกรมที่เกิดขึ้นพร้อมกันหลายรายการ ThreadLocal มีบทบาทสำคัญมาก มันไม่ได้เพิ่มล็อคและแนบเธรดได้อย่างง่ายดายอย่างราบรื่นโดยไม่ต้องจัดสรรพื้นที่ใหม่ทุกครั้งเช่นตัวแปรท้องถิ่น เนื่องจากช่องว่างจำนวนมากมีความปลอดภัยจากเธรดพวกเขาสามารถใช้บัฟเฟอร์เธรดส่วนตัวซ้ำ ๆ ได้
วิธีใช้ Threadlocal?
กำหนดตัวแปร Threadlocal ในตำแหน่งที่เหมาะสมในระบบซึ่งสามารถกำหนดเป็นประเภทคงที่สาธารณะ (วัตถุ ThreadLocal เป็นใหม่โดยตรง) หากคุณต้องการใส่ข้อมูลลงไปใช้ชุด (วัตถุ) ใช้การดำเนินการ GET () และใช้ remove () เมื่อลบองค์ประกอบ วิธีอื่น ๆ เป็นวิธีที่ไม่ใช่แบบสาธารณะและไม่แนะนำ
นี่คือตัวอย่างง่ายๆ (รหัสตัวอย่าง 1):
คลาสสาธารณะ ThreadLocalTest2 {สาธารณะสุดท้ายคงที่ threadLocal <String> test_thread_name_local = ใหม่ ThreadLocal <String> (); สาธารณะสุดท้ายคงที่ threadlocal <String> test_thread_value_local = new ThreadLocal <String> (); โมฆะคงที่สาธารณะหลัก (สตริง [] args) {สำหรับ (int i = 0; i <100; i ++) {ชื่อสตริงสุดท้าย = "เธรด-[" + i + "]"; ค่าสตริงสุดท้าย = string.valueof (i); เธรดใหม่ () {โมฆะสาธารณะเรียกใช้ () {ลอง {test_thread_name_local.set (ชื่อ); test_thread_value_local.set (ค่า); Calla (); } ในที่สุด {test_thread_name_local.remove (); test_thread_value_local.remove (); } } } }.เริ่ม(); }} โมฆะคงที่สาธารณะ calla () {callb (); } โมฆะคงที่สาธารณะ callb () {ใหม่ ThreadLocalTest2 (). callc (); } โมฆะสาธารณะ callc () {calld (); } โมฆะสาธารณะ calld () {system.out.println (test_thread_name_local.get () + "/t = t" + test_thread_value_local.get ()); -ที่นี่เราจำลอง 100 เธรดเพื่อเข้าถึงชื่อและค่าตามลำดับ ค่าของชื่อและค่าถูกตั้งค่าโดยเจตนาเหมือนกันตรงกลางเพื่อดูว่ามีปัญหาพร้อมกันหรือไม่ ผ่านเอาต์พุตเราจะเห็นว่าเอาต์พุตเธรดไม่ได้ส่งออกตามลำดับซึ่งหมายความว่ามันถูกดำเนินการในแบบขนานและชื่อและค่าเธรดสามารถสอดคล้องกัน ในช่วงกลางผ่านหลายวิธีพารามิเตอร์จะไม่ถูกส่งผ่านในการโทรจริงดังนั้นวิธีการรับตัวแปรที่เกี่ยวข้อง อย่างไรก็ตามในระบบจริงพวกเขามักจะข้ามชั้นเรียน ที่นี่พวกเขาจะจำลองในชั้นเดียวเท่านั้น ในความเป็นจริงคลาสข้ามเป็นผลลัพธ์เดียวกัน คุณสามารถจำลองพวกเขาด้วยตัวเอง
ฉันเชื่อว่าหลังจากได้เห็นสิ่งนี้โปรแกรมเมอร์หลายคนสนใจหลักการของ Threadlocal มาดูกันว่ามันทำอย่างไร แม้ว่าพารามิเตอร์จะไม่ผ่าน แต่พวกเขาสามารถใช้มันเช่นตัวแปรท้องถิ่น มันค่อนข้างมหัศจรรย์ ในความเป็นจริงคุณสามารถบอกได้ว่ามันเป็นวิธีการตั้งค่า เมื่อคุณเห็นว่าชื่อควรเกี่ยวข้องกับเธรดจากนั้นพูดเรื่องไร้สาระน้อยลงลองมาดูซอร์สโค้ด เนื่องจากเราใช้ชุดมากที่สุดรับและลบจากนั้นเริ่มต้นด้วยชุด:
วิธีการตั้งค่า (t obj) คือ (รหัสตัวอย่าง 2):
ชุดโมฆะสาธารณะ (ค่า t) {เธรด t = เธรด currentthread (); threadlocalMap map = getMap (t); if (map! = null) map.set (นี้ค่า); else createMap (t, value);}ก่อนอื่นเธรดปัจจุบันจะได้รับเช่นเดียวกับการเดาและจากนั้นมีวิธี getMap ซึ่งผ่านในเธรดปัจจุบัน ก่อนอื่นเราสามารถเข้าใจได้ว่าแผนที่นี้เป็นแผนที่ที่เกี่ยวข้องกับเธรด ถัดไปหากไม่ว่างเปล่าให้ดำเนินการตั้งค่า เมื่อคุณติดตามมันคุณจะพบว่าสิ่งนี้คล้ายกับการทำงานของ HashMap นั่นคือชิ้นส่วนของข้อมูลที่เขียนลงในแผนที่ หากว่างเปล่าวิธีการ CreateMap จะถูกเรียก หลังจากเข้ามาลองดู (รหัสตัวอย่าง 3):
เป็นโมฆะ createMap (เธรด t, t FirstValue) {t.threadLocals = new ThreadLocalMap (นี่, FirstValue);}เงินคืนสร้าง threadlocalmap และเขียนพารามิเตอร์ที่ผ่านและ threadlocal ปัจจุบันเป็นโครงสร้าง kv (รหัสตัวอย่าง 4):
ThreadlocalMap (ThreadLocal Finskey, Object FirstValue) {table = รายการใหม่ [เริ่มต้น _capacity]; int i = firstKey.threadLocalHashCode & (initial_capacity - 1); ตาราง [i] = รายการใหม่ (FirstKey, FirstValue); ขนาด = 1; SetThreshold (initial_capacity);}สิ่งนี้ไม่ได้อธิบายไว้ที่นี่รายละเอียดโครงสร้างของ ThreadLocalMap คุณเพียงแค่ต้องรู้ว่าการใช้งานนั้นคล้ายกับ HashMap มีหลายวิธีที่ไม่มีแผนที่การใช้งานเนื่องจากไม่ต้องการให้คุณได้รับแผนที่ผ่านวิธีการบางอย่าง (เช่นการสะท้อน) เพื่อดำเนินการต่อไป มันเป็นคลาสภายในแบบคงที่ใน ThreadLocal, ประเภทเริ่มต้นและคลาสเฉพาะภายใต้ Java.lang เท่านั้นที่สามารถอ้างถึงได้ดังนั้นคุณสามารถนึกถึงเธรดที่สามารถอ้างถึงได้
ลองย้อนกลับไปที่วิธี getMap เพราะฉันรู้ว่าแผนที่ที่ได้รับนั้นเกี่ยวข้องกับเธรดและผ่านรหัสตัวอย่างที่ 3 มี t.threadlocalmap = ใหม่ ThreadLocalMap (นี่คือ FirstValue) ฉันเชื่อว่าคุณควรเข้าใจว่าตัวแปรนี้ควรมาจากเธรด ไปตามวิธี getMap:
threadlocalmap getMap (เธรด t) {return t.threadlocals;}ใช่มันมาจากเธรดและเธรดนี้เกิดขึ้นเป็นเธรดปัจจุบันดังนั้นเข้าไปข้างในและดูคำจำกัดความ:
threadlocal.threadLocalMap threadLocals = null;
คุณสมบัตินี้อยู่ในคลาสเธรดนั่นคือแต่ละเธรดมี ThreadLocalMap โดยค่าเริ่มต้นซึ่งใช้ในการจัดเก็บตัวแปรท้องถิ่นระดับเธรด โดยปกติคุณไม่สามารถกำหนดค่าให้กับมันได้เนื่องจากการมอบหมายดังกล่าวมักจะไม่ปลอดภัย
ดูเหมือนว่าจะยุ่งนิดหน่อยไม่ต้องกังวลลองมองย้อนกลับไปสำรวจความคิด:
1. มีคุณสมบัติในเธรดที่คล้ายกับ HashMap แต่ชื่อของมันคือ ThreadLocalMap คุณสมบัตินี้เป็นประเภทเริ่มต้นดังนั้นคลาสทั้งหมดภายใต้แพ็คเกจเดียวกันสามารถอ้างอิงได้ เนื่องจากเป็นตัวแปรท้องถิ่นของเธรดแต่ละเธรดมีแผนที่แยกต่างหากซึ่งไม่ขัดแย้งกันดังนั้นแม้ว่า ThreadLocal จะถูกกำหนดให้เป็นเธรดคงที่จะไม่มีความขัดแย้ง
2. Threadlocal และเธรดอยู่ภายใต้แพ็คเกจเดียวกัน คุณสามารถอ้างถึงคลาสนี้และใช้งานได้ ในเวลานี้แต่ละ ThreadLocal กำหนดหนึ่งใช้เป็นคีย์และค่าที่คุณส่งผ่านเป็นค่าและนี่คือ ThreadLocal ที่คุณกำหนด ดังนั้นตัวแปร ThreadLocal ที่แตกต่างกันจึงใช้ชุดและข้อมูลระหว่างกันจะไม่ขัดแย้งกันเพราะปุ่มของพวกเขาแตกต่างกัน แน่นอนหลังจาก ThreadLocal เดียวกันทำการดำเนินการสองชุดครั้งสุดท้ายจะเป็นที่นิยม
3. หากต้องการรวมเมื่อเธรดเป็นแบบขนานเธรดล็อกสามารถใช้งานได้เช่นตัวแปรท้องถิ่นและเป็นเธรดที่ปลอดภัยและข้อมูลระหว่างตัวแปร ThreadLocal ที่แตกต่างกันไม่มีความขัดแย้ง
มาดูวิธี GET และลบวิธีต่อไปมันง่ายจริง ๆ :
สาธารณะ t get () {เธรด t = thread.currentthread (); threadlocalMap map = getMap (t); if (map! = null) {threadlocalmap.entry e = map.getEntry (นี่); ถ้า (e! = null) return (t) e.value; } return setInitialValue ();}โดยการเรียกใช้วิธี getMap ตามเธรดปัจจุบันนั่นคือ t.threadlocalmap เรียกว่าแล้วค้นหาในแผนที่โปรดทราบว่าแผนที่นั้นพบได้ด้วยรายการนั่นคือโครงสร้างพื้นฐานของ KV เพราะคุณเขียนไปยังค่าเท่านั้นมันจะตั้งค่า e.value เพื่อส่งคืนค่าที่คุณเขียน คุณจะเห็นได้ว่า Map.getEntry นั้นได้มาจากสิ่งนี้
วิธีการลบเดียวกันคือ:
โมฆะสาธารณะลบ () {threadlocalmap m = getMap (thread.currentthread ()); ถ้า (m! = null) m.remove (นี่);}นอกจากนี้แผนที่ยังได้มาจากเธรดปัจจุบัน หากไม่ว่างเปล่าให้ลบและลบผ่านสิ่งนี้
นอกจากนี้ (2013-6-29) ข้อผิดพลาดของการลืมเขียนคืออะไร? มีข้อผิดพลาดอะไรบ้างใน Thish Local นี้? คุณควรเห็นจากตัวอย่างก่อนหน้าว่าวัตถุที่เกี่ยวข้องกับเธรดจะถูกผูกไว้กับแผนที่และแผนที่นี้เป็นคุณสมบัติของเธรดเธรด จากนั้นมีปัญหาที่ถ้าคุณไม่ลบตัวเองหรือหากคุณไม่ทราบว่าจะลบเมื่อใดในโปรแกรมของคุณเองแล้วเธรดจะไม่ถูกลงชื่อเข้าใช้และชุดข้อมูลจะไม่ถูกออกจากระบบ
ในทางกลับกันเว้นแต่คุณจะเข้าใจอย่างชัดเจนว่าควรตั้งค่าวัตถุนี้และที่ไหนที่จะลบ หากคลุมเครือเป็นไปได้ว่ารหัสของคุณจะไม่ไปที่ตำแหน่งลบหรือทำให้เกิดปัญหาเชิงตรรกะ นอกจากนี้หากไม่ได้ลบออกคุณต้องรอให้เธรดออกจากระบบ ในเซิร์ฟเวอร์แอปพลิเคชันหลายตัวเธรดจะถูกนำกลับมาใช้ใหม่เนื่องจากยังมีค่าใช้จ่ายในเธรดการจัดสรรเคอร์เนลดังนั้นจึงเป็นเรื่องยากสำหรับเธรดที่จะออกจากระบบในแอปพลิเคชันเหล่านี้ จากนั้นข้อมูลที่เขียนไปยัง ThreadLocal นั้นไม่ใช่เรื่องง่ายที่จะออกจากระบบ สิ่งเหล่านี้อาจถูกซ่อนไว้โดยไม่ตั้งใจและใช้เมื่อเราใช้เฟรมเวิร์กโอเพนซอร์สซึ่งอาจทำให้เกิดปัญหา ในที่สุดฉันก็พบว่าเมื่อ oom ข้อมูลมาจาก ThreadLocalMap ฉันไม่รู้ว่าข้อมูลนี้ตั้งอยู่ที่ไหนดังนั้นคุณควรให้ความสนใจกับหลุมนี้และมากกว่าหนึ่งคนได้ตกอยู่ในหลุมนี้