สรุป
ซีรี่ส์นี้ขึ้นอยู่กับเส้นทางของการกลั่นตัวเลขเป็นทองคำและเพื่อเรียนรู้ที่ดีขึ้นมีการบันทึกชุด บทความนี้ส่วนใหญ่แนะนำ: 1. ความคิดและวิธีการเพิ่มประสิทธิภาพล็อค 2. การเพิ่มประสิทธิภาพล็อคในเครื่องเสมือน 3. กรณีการใช้งานล็อคที่ไม่ถูกต้อง 4. ThreadLocal และการวิเคราะห์รหัสแหล่งที่มาของมัน
1. แนวคิดและวิธีการเพิ่มประสิทธิภาพล็อค
ระดับของการเกิดขึ้นพร้อมกันถูกกล่าวถึงในบทนำสู่ [การพร้อมกันสูง Java 1]
เมื่อใช้การล็อคแล้วหมายความว่าสิ่งนี้กำลังปิดกั้นดังนั้นการเกิดพร้อมกันโดยทั่วไปจะต่ำกว่าสถานการณ์ที่ปราศจากล็อคเล็กน้อย
การเพิ่มประสิทธิภาพการล็อคที่กล่าวถึงที่นี่หมายถึงวิธีการป้องกันไม่ให้ประสิทธิภาพแย่เกินไปในกรณีของการบล็อก แต่ไม่ว่าคุณจะปรับให้เหมาะสมได้อย่างไรประสิทธิภาพโดยทั่วไปจะเลวร้ายยิ่งกว่าสถานการณ์ที่ปราศจากล็อค
ควรสังเกตที่นี่ว่า trylock ใน reentrantlock ที่กล่าวถึงใน [Java v] แพ็คเกจพร้อมกัน JDK ที่เกิดขึ้นพร้อมกันสูง 1 มีแนวโน้มที่จะเป็นวิธีที่ปราศจากล็อคเพราะมันจะไม่แขวนไว้เมื่อผู้พิพากษา Trylock
เพื่อสรุปความคิดและวิธีการเพิ่มประสิทธิภาพล็อคมีประเภทต่อไปนี้
1.1 ลดเวลาการล็อค
โมฆะที่ซิงโครไนซ์สาธารณะ syncMethod () {otherCode1 (); mutextmethod (); otherCode2 (); - เช่นเดียวกับรหัสด้านบนคุณต้องล็อคก่อนที่จะป้อนวิธีการและเธรดอื่น ๆ ต้องรออยู่ข้างนอก
จุดเพิ่มประสิทธิภาพที่นี่คือการลดเวลารอของเธรดอื่น ๆ ดังนั้นจึงใช้เพื่อเพิ่มล็อคในโปรแกรมที่มีข้อกำหนดด้านความปลอดภัยของเธรด
โมฆะสาธารณะ syncMethod () {otherCode1 (); ซิงโครไนซ์ (นี่) {mutextmethod (); } otherCode2 (); -1.2 ลดขนาดอนุภาคล็อค
แยกวัตถุขนาดใหญ่ (วัตถุนี้อาจเข้าถึงได้โดยหลายเธรด) เป็นวัตถุขนาดเล็กเพิ่มความเท่าเทียมอย่างมากและลดการแข่งขันล็อค โดยการลดการแข่งขันสำหรับล็อคและการให้น้ำหนักต่อล็อคอัตราความสำเร็จของล็อคน้ำหนักเบาจะได้รับการปรับปรุง
กรณีทั่วไปของการลดความละเอียดของล็อคคือพร้อมกัน สิ่งนี้ถูกกล่าวถึงในแพ็คเกจพร้อมกันของ [การพร้อมกันสูง Java V] JDK พร้อมกัน 1
1.3 การแยกล็อค
การแยกล็อคที่พบบ่อยที่สุดคือ ReadWriteLock Read-Write Lock ซึ่งแยกออกเป็นล็อคอ่าน-เขียนและเขียนล็อคตามฟังก์ชั่น ด้วยวิธีนี้การอ่านและการอ่านไม่ได้เกิดขึ้นร่วมกันการอ่านและการเขียนเป็นพิเศษซึ่งช่วยให้มั่นใจถึงความปลอดภัยของด้ายและปรับปรุงประสิทธิภาพ สำหรับรายละเอียดโปรดตรวจสอบ [High Concurrency Java V] แพ็คเกจพร้อมกัน JDK JDK 1
แนวคิดของการแยกการอ่านและการเขียนสามารถขยายออกไปและตราบใดที่การดำเนินการไม่ส่งผลกระทบต่อกันและกันสามารถแยกล็อคได้
ตัวอย่างเช่น linkedblockingqueue
นำมันออกมาจากหัวและวางข้อมูลจากหาง แน่นอนว่ามันคล้ายกับการขโมยงานใน ForkJoinpool ที่กล่าวถึงใน [Java VI ที่พร้อมกันสูง] JDK พร้อมกันแพ็คเกจ 2
1.4 ล็อคครรภ์
โดยทั่วไปเพื่อให้แน่ใจว่ามีประสิทธิภาพพร้อมกันระหว่างหลายเธรดแต่ละเธรดจะต้องถือการล็อคให้สั้นที่สุดเท่าที่จะเป็นไปได้นั่นคือการล็อคควรจะถูกปล่อยออกมาทันทีหลังจากใช้ทรัพยากรสาธารณะ ด้วยวิธีนี้เธรดอื่น ๆ ที่รอการล็อคนี้ได้รับทรัพยากรเพื่อดำเนินการงานโดยเร็วที่สุด อย่างไรก็ตามทุกอย่างมีระดับ หากมีการร้องขอล็อคเดียวกันอย่างต่อเนื่องซิงโครไนซ์และปล่อยออกมามันจะใช้ทรัพยากรที่มีค่าของระบบซึ่งไม่เอื้อต่อการเพิ่มประสิทธิภาพประสิทธิภาพ
ตัวอย่างเช่น:
โมฆะสาธารณะ demomethod () {ซิงโครไนซ์ (ล็อค) {// do sth } // ทำงานซิงโครไนซ์อื่น ๆ ที่ไม่พึงประสงค์ แต่สามารถดำเนินการซิงโครไนซ์ได้อย่างรวดเร็ว (ล็อค) {// do sth - ในกรณีนี้ตามความคิดของการล็อคครรภ์มันควรจะรวมเข้าด้วยกัน
โมฆะสาธารณะ demomethod () {// รวมเข้ากับคำขอล็อคที่ซิงโครไนซ์ (ล็อค) {// do sth // ทำงานซิงโครไนซ์อื่น ๆ ที่ไม่ต้องการ แต่สามารถดำเนินการได้อย่างรวดเร็ว}} แน่นอนว่ามีข้อกำหนดเบื้องต้นงานตรงกลางที่ไม่ต้องการการซิงโครไนซ์จะถูกดำเนินการอย่างรวดเร็ว
ให้ฉันยกตัวอย่างสุดโต่งให้คุณอีก:
สำหรับ (int i = 0; i <circle; i ++) {ซิงโครไนซ์ (ล็อค) {}} ล็อคจะต้องได้รับในลูป แม้ว่า JDK จะเพิ่มประสิทธิภาพรหัสนี้ภายใน แต่ก็เป็นการดีกว่าที่จะเขียนโดยตรง
ซิงโครไนซ์ (ล็อค) {สำหรับ (int i = 0; i <circle; i ++) {}} แน่นอนถ้ามีความจำเป็นที่จะต้องบอกว่าลูปดังกล่าวยาวเกินไปและคุณต้องให้เธรดอื่น ๆ ไม่ต้องรอนานเกินไปคุณสามารถเขียนได้ตามที่กล่าวมาข้างต้นเท่านั้น หากไม่มีข้อกำหนดที่คล้ายกันดังกล่าวจะเป็นการดีกว่าที่จะเขียนลงในข้อกำหนดต่อไปนี้โดยตรง
1.5 การกำจัดล็อค
การกำจัดล็อคเป็นสิ่งที่ระดับคอมไพเลอร์
ในคอมไพเลอร์ทันทีหากพบวัตถุที่ไม่สามารถแชร์ได้การดำเนินการล็อคของวัตถุเหล่านี้สามารถกำจัดได้
บางทีคุณอาจพบว่ามันแปลกที่เนื่องจากวัตถุบางอย่างไม่สามารถเข้าถึงได้หลายเธรดทำไมฉันต้องเพิ่มล็อค? จะดีกว่าหรือไม่ถ้าไม่เพิ่มล็อคเมื่อเขียนโค้ด
แต่บางครั้งล็อคเหล่านี้ไม่ได้เขียนโดยโปรแกรมเมอร์ บางคนมีล็อคในการใช้งาน JDK เช่นคลาสเช่นเวกเตอร์และ StringBuffer วิธีการหลายอย่างของพวกเขามีล็อค เมื่อเราใช้วิธีการของคลาสเหล่านี้โดยไม่มีความปลอดภัยของเธรดเมื่อมีเงื่อนไขบางอย่างคอมไพเลอร์จะลบการล็อคเพื่อปรับปรุงประสิทธิภาพ
ตัวอย่างเช่น:
โมฆะคงที่สาธารณะหลัก (String args []) พ่น InterruptedException {Long Start = System.currentTimeMillis (); สำหรับ (int i = 0; i <2000000; i ++) {createstringBuffer ("JVM", "การวินิจฉัย"); } long bufferCost = system.currentTimeMillis () - เริ่ม; System.out.println ("craetestringbuffer:" + buffercost + "ms"); } สตริงคงที่สาธารณะ createstringBuffer (สตริง S1, สตริง S2) {StringBuffer sb = new StringBuffer (); SB.Append (S1); SB.Append (S2); ส่งคืน sb.toString (); - StringBuffer.Append ในโค้ดด้านบนเป็นการดำเนินการแบบซิงโครนัส แต่ StringBuffer เป็นตัวแปรท้องถิ่นและวิธีการไม่ส่งคืน StringBuffer ดังนั้นจึงเป็นไปไม่ได้ที่จะหลายเธรดเพื่อเข้าถึง
จากนั้นการดำเนินการซิงโครไนซ์ใน StringBuffer นั้นไม่มีความหมายในเวลานี้
การยกเลิกล็อคถูกตั้งค่าบนพารามิเตอร์ JVM แน่นอนว่าต้องอยู่ในโหมดเซิร์ฟเวอร์:
-server -xx:+DoScapeanalysis -xx:+Eliminatelocks
และเปิดการวิเคราะห์หลบหนี ฟังก์ชั่นของการวิเคราะห์หลบหนีคือการดูว่าตัวแปรมีแนวโน้มที่จะหลบหนีจากขอบเขตหรือไม่
ตัวอย่างเช่นใน StringBuffer ด้านบนการกลับมาของ CraetestringBuffer ในรหัสด้านบนเป็นสตริงดังนั้นสตริงตัวแปรท้องถิ่นนี้จะไม่ถูกนำมาใช้ที่อื่น หากคุณเปลี่ยน CraetestringBuffer เป็น
public Stringbuffer Public CraetestringBuffer (String S1, String S2) {StringBuffer SB = new StringBuffer (); SB.Append (S1); SB.Append (S2); กลับ SB; - จากนั้นหลังจากส่งคืนสตริงนี้มันอาจจะถูกใช้ที่อื่น (ตัวอย่างเช่นฟังก์ชั่นหลักจะส่งคืนผลลัพธ์และใส่ลงในแผนที่ ฯลฯ ) จากนั้นการวิเคราะห์ Escape JVM สามารถวิเคราะห์ได้ว่า Stringbuffer ตัวแปรท้องถิ่นนี้หนีขอบเขตของมัน
ดังนั้นจากการวิเคราะห์การหลบหนี JVM สามารถตัดสินได้ว่าหากสตริงบัฟเฟอร์ตัวแปรท้องถิ่นไม่หลบหนีขอบเขตก็สามารถพิจารณาได้ว่าสตริงบัฟเฟอร์จะไม่สามารถเข้าถึงได้หลายเธรดและจากนั้นล็อคพิเศษเหล่านี้สามารถลบออกเพื่อปรับปรุงประสิทธิภาพ
เมื่อพารามิเตอร์ JVM คือ:
-server -xx:+DoScapeanalysis -xx:+Eliminatelocks
เอาท์พุท:
CraetestringBuffer: 302 ms
พารามิเตอร์ JVM คือ:
-server -xx:+DoScapeanalysis -xx: -Eliminatelocks
เอาท์พุท:
CraetestringBuffer: 660 ms
เห็นได้ชัดว่าเอฟเฟกต์การกำจัดล็อคยังคงชัดเจนมาก
2. ล็อคการเพิ่มประสิทธิภาพในเครื่องเสมือน
ก่อนอื่นเราต้องแนะนำส่วนหัวของวัตถุ ใน JVM แต่ละวัตถุมีส่วนหัวของวัตถุ
ทำเครื่องหมายคำ, เครื่องหมายสำหรับส่วนหัววัตถุ, 32 บิต (ระบบ 32 บิต)
อธิบายแฮชข้อมูลล็อคแท็กคอลเลกชันขยะอายุ
นอกจากนี้ยังจะบันทึกตัวชี้ไปยังเร็กคอร์ดล็อคตัวชี้ไปยังจอมอนิเตอร์รหัสเธรดล็อคอคติ ฯลฯ
พูดง่ายๆคือส่วนหัวของวัตถุคือการบันทึกข้อมูลที่เป็นระบบบางอย่าง
2.1 ล็อคบวก
อคติที่เรียกว่าคือความผิดปกตินั่นคือการล็อคจะมีแนวโน้มไปยังเธรดที่เป็นเจ้าของล็อคในปัจจุบัน
ในกรณีส่วนใหญ่ไม่มีการแข่งขัน (ในกรณีส่วนใหญ่บล็อกการซิงโครไนซ์ไม่มีหลายเธรดในเวลาเดียวกันล็อคการแข่งขัน) ดังนั้นประสิทธิภาพสามารถปรับปรุงได้โดยการให้น้ำหนัก นั่นคือเมื่อไม่มีการแข่งขันเมื่อเธรดที่ได้รับการล็อคก่อนหน้านี้จะได้รับการล็อคอีกครั้งมันจะตรวจสอบว่าล็อคชี้ไปที่ฉันดังนั้นเธรดจะไม่จำเป็นต้องได้รับการล็อคอีกครั้งและสามารถป้อนบล็อกการซิงโครไนซ์โดยตรง
การใช้งานการล็อคอคติคือการตั้งค่าเครื่องหมายของเครื่องหมายส่วนหัวของวัตถุเป็นแบบเอนเอียงและเขียน ID เธรดไปยังเครื่องหมายส่วนหัวของวัตถุ
เมื่อเธรดอื่นขอล็อคเดียวกันโหมดอคติจะสิ้นสุดลง
JVM เปิดใช้งานการล็อคอคติตามค่าเริ่มต้น -xx:+usebiasedlocking
ในการแข่งขันที่ดุเดือดการล็อคลำเอียงจะเพิ่มภาระของระบบ (การตัดสินว่ามีการเพิ่มลำเอียงว่ามีการเพิ่มทุกครั้ง)
ตัวอย่างของการล็อคลำเอียง:
การทดสอบแพ็คเกจ; นำเข้า java.util.list; นำเข้า java.util.vector; การทดสอบระดับสาธารณะ {รายการคงที่สาธารณะ <จำนวนเต็ม> numberList = เวกเตอร์ใหม่ <integer> (); โมฆะคงที่สาธารณะหลัก (String [] args) พ่น InterruptedException {long start = system.currentTimeMillis (); จำนวน int = 0; int startNum = 0; ในขณะที่ (นับ <10,00000000) {numberlist.add (startnum); StartNum += 2; นับ ++; } Long End = System.currentTimeMillis (); System.out.println (สิ้นสุด - เริ่มต้น); - เวกเตอร์เป็นคลาสที่ปลอดภัยจากเธรดที่ใช้กลไกการล็อคภายใน ทุกครั้งที่เพิ่มคำขอล็อคจะทำ รหัสด้านบนมีเพียงเธรดหลักเดียวจากนั้นเพิ่มคำขอล็อคซ้ำ ๆ ซ้ำ ๆ
ใช้พารามิเตอร์ JVM ต่อไปนี้เพื่อตั้งค่าการล็อคอคติ:
-xx:+usebiasedlocking -xx: ลำเอียง allockingstartupdelay = 0
การเปิดใช้งาน BiasedLockingStartUpDelay หมายความว่าการเปิดใช้งานการล็อคอคติหลังจากระบบเริ่มต้นเป็นเวลาสองสามวินาที ค่าเริ่มต้นคือ 4 วินาทีเนื่องจากเมื่อระบบเริ่มต้นขึ้นการแข่งขันข้อมูลทั่วไปค่อนข้างรุนแรง เปิดใช้งานการล็อคอคติในเวลานี้จะลดประสิทธิภาพ
ตั้งแต่ที่นี่เพื่อทดสอบประสิทธิภาพของการล็อคอคติเวลาล็อคอคติล่าช้าจะถูกตั้งค่าเป็น 0
ในเวลานี้ผลลัพธ์คือ 9209
ปิดล็อคอคติด้านล่าง:
-xx: -usebiasedlocking
เอาท์พุทคือ 9627
โดยทั่วไปเมื่อไม่มีการแข่งขันประสิทธิภาพของการเปิดใช้งานการล็อคอคติจะได้รับการปรับปรุงประมาณ 5%
2.2 ล็อคน้ำหนักเบา
ความปลอดภัยแบบมัลติเธรดมัลติเธรดของ Java ถูกนำไปใช้ตามกลไกการล็อคและประสิทธิภาพของการล็อคมักจะไม่เป็นที่น่าพอใจ
เหตุผลก็คือ MonitorEnenter และ Monitorexit ซึ่งเป็นสองไบต์ดั้งเดิมที่ควบคุมการซิงโครไนซ์มัลติเธรดได้ถูกนำมาใช้โดย JVM ใช้ Mutex สำหรับระบบปฏิบัติการ
Mutex เป็นการดำเนินการที่ค่อนข้างใช้ทรัพยากรซึ่งทำให้เธรดแขวนและจำเป็นต้องจัดตารางเวลากลับไปเป็นเธรดดั้งเดิมในช่วงเวลาสั้น ๆ
เพื่อเพิ่มประสิทธิภาพกลไกการล็อคของ Java แนวคิดของการล็อคน้ำหนักเบาได้รับการแนะนำตั้งแต่ Java6
การล็อคน้ำหนักเบามีวัตถุประสงค์เพื่อลดโอกาสของการทำมัลติเธรดที่เข้าสู่ Mutex ไม่ใช่เพื่อแทนที่ mutex
มันใช้ CPU Primitive Compare-and-Swap (CAS, Assembly Assummbly คำแนะนำ CMPXCHG) และพยายามแก้ไขก่อนเข้าสู่ MUTEX
หากการล็อคอคติล้มเหลวระบบจะดำเนินการล็อคน้ำหนักเบา จุดประสงค์ของการดำรงอยู่ของมันคือการหลีกเลี่ยงการใช้ Mutex ในระดับระบบปฏิบัติการให้มากที่สุดเท่าที่จะเป็นไปได้เพราะประสิทธิภาพนั้นค่อนข้างแย่ เนื่องจาก JVM เองเป็นแอปพลิเคชันฉันหวังว่าจะแก้ปัญหาการซิงโครไนซ์เธรดในระดับแอปพลิเคชัน
เพื่อสรุปการล็อคน้ำหนักเบาเป็นวิธีการล็อคที่รวดเร็ว ก่อนเข้าสู่ Mutex ให้ใช้การดำเนินการ CAS เพื่อพยายามเพิ่มล็อค พยายามอย่าใช้ Mutex ในระดับระบบปฏิบัติการเพื่อปรับปรุงประสิทธิภาพ
จากนั้นเมื่อล็อคอคติล้มเหลวขั้นตอนของการล็อคน้ำหนักเบา:
1. บันทึกตัวชี้เครื่องหมายของส่วนหัววัตถุลงในวัตถุที่ถูกล็อค (วัตถุที่นี่หมายถึงวัตถุที่ถูกล็อคเช่นซิงโครไนซ์ (นี่) {} นี่คือวัตถุที่นี่)
Lock-> set_displaced_header (mark);
2. ตั้งส่วนหัววัตถุเป็นตัวชี้ไปยังล็อค (ในพื้นที่สแต็กเธรด)
if (mark == (Markoop) อะตอม :: cmpxchg_ptr (ล็อค, obj ()-> mark_addr (), mark)) {tevent (slow_enter: release stacklock); กลับ ; - ล็อคอยู่ในสแต็กเธรด ดังนั้นเพื่อตรวจสอบว่าเธรดถือล็อคนี้เพียงแค่ตรวจสอบว่าพื้นที่ที่ชี้ไปที่ส่วนหัวของวัตถุนั้นอยู่ในพื้นที่ที่อยู่ของสแต็กเธรดหรือไม่
หากการล็อคน้ำหนักเบาล้มเหลวหมายความว่ามีการแข่งขันและอัพเกรดเป็นล็อคเฮฟวี่เวท (ล็อคปกติ) ซึ่งเป็นวิธีการซิงโครไนซ์ที่ระดับระบบปฏิบัติการ ในกรณีที่ไม่มีการแข่งขันล็อคล็อคน้ำหนักเบาจะลดการสูญเสียประสิทธิภาพที่เกิดจากล็อคแบบดั้งเดิมโดยใช้ OS Mutexes เมื่อการแข่งขันนั้นรุนแรงมาก (ล็อคที่มีน้ำหนักเบามักจะล้มเหลว) ล็อคที่มีน้ำหนักเบาจะทำการดำเนินการพิเศษจำนวนมากส่งผลให้ประสิทธิภาพลดลง
2.3 สปินล็อค
เมื่อการแข่งขันมีอยู่เนื่องจากความพยายามล็อคน้ำหนักเบาล้มเหลวอาจได้รับการอัพเกรดโดยตรงเป็นล็อคเฮฟวี่เวทเพื่อใช้การยกเว้นระดับระบบปฏิบัติการซึ่งกันและกัน นอกจากนี้ยังเป็นไปได้ที่จะลองสปินล็อคอีกครั้ง
หากเธรดสามารถรับล็อคได้อย่างรวดเร็วคุณไม่สามารถแขวนเธรดที่เลเยอร์ OS ให้เธรดทำการทำงานที่ว่างเปล่า (หมุน) หลายครั้งและพยายามล็อคอย่างต่อเนื่อง (คล้ายกับ Trylock) แน่นอนว่าจำนวนลูปมี จำกัด เมื่อจำนวนลูปมาถึงมันจะยังคงได้รับการอัพเกรดเป็นล็อคเฮฟวี่เวท ดังนั้นเมื่อแต่ละเธรดมีเวลาน้อยในการล็อคล็อคสปินล็อคสามารถพยายามหลีกเลี่ยงเธรดที่ถูกระงับที่เลเยอร์ OS
เปิดใช้งาน JDK1.6 -xx:+usespinning
ใน JDK1.7 ให้ลบพารามิเตอร์นี้และเปลี่ยนเป็นการใช้งานในตัว
หากบล็อกการซิงโครไนซ์ยาวมากและการหมุนล้มเหลวประสิทธิภาพของระบบจะลดลง หากบล็อกการซิงโครไนซ์สั้นมากและการหมุนจะสำเร็จจะช่วยประหยัดเวลาสลับช่วงล่างของเธรดและปรับปรุงประสิทธิภาพของระบบ
2.4 ล็อคบวกล็อคน้ำหนักเบาสรุปสปินล็อค
ล็อคข้างต้นไม่ใช่วิธีการเพิ่มประสิทธิภาพการล็อคระดับภาษาชวา แต่ถูกสร้างขึ้นใน JVM
ก่อนอื่นล็อคการให้น้ำหนักคือการหลีกเลี่ยงการใช้ประสิทธิภาพของเธรดเมื่อได้รับ/ปล่อยล็อคเดียวกันซ้ำ ๆ หากเธรดเดียวกันยังคงได้รับการล็อคนี้มันจะเข้าสู่บล็อกการซิงโครไนซ์โดยตรงเมื่อพยายามอคติล็อคและไม่จำเป็นต้องได้รับการล็อคอีกครั้ง
ล็อคน้ำหนักเบาและล็อคสปินมีวัตถุประสงค์เพื่อหลีกเลี่ยงการโทรโดยตรงไปยังการดำเนินการ Mutex ในระดับระบบปฏิบัติการเนื่องจากการระงับเธรดเป็นการดำเนินการที่ใช้ทรัพยากรมาก
เพื่อหลีกเลี่ยงการใช้ล็อคเฮฟวี่เวท (Mutex ที่ระดับระบบปฏิบัติการ) เราจะลองล็อคน้ำหนักเบาก่อน ล็อคน้ำหนักเบาจะพยายามใช้การดำเนินการ CAS เพื่อรับล็อค หากล็อคน้ำหนักเบาไม่สามารถรับได้นั่นหมายความว่ามีการแข่งขัน แต่บางทีคุณอาจจะได้ล็อคเร็ว ๆ นี้และคุณจะลองสปินล็อคทำลูปว่างเปล่าสองสามตัวบนเธรดและลองล็อคทุกครั้งที่คุณวนเวียน หากสปินล็อคล้มเหลวก็สามารถอัพเกรดเป็นล็อคเฮฟวี่เวทเท่านั้น
จะเห็นได้ว่าล็อคลำเอียงล็อคน้ำหนักเบาและล็อคหมุนล็อคล็อคในแง่ดีทั้งหมด
3. กรณีของการใช้ล็อคไม่ถูกต้อง
ชั้นเรียนสาธารณะ IntegerLock {Integer แบบคงที่ i = 0; คลาสคงที่ระดับสาธารณะ AddThread ขยายเธรด {public void run () {สำหรับ (int k = 0; k <100000; k ++) {ซิงโครไนซ์ (i) {i ++; }}}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่น InterruptedException {addThread t1 = ใหม่ addThread (); AddThread T2 = ใหม่ addThread (); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); - ความผิดพลาดขั้นพื้นฐานมากคือในรูปแบบการออกแบบพร้อมกัน [Java VII ที่เกิดขึ้นพร้อมกันสูง] Interger จะไม่เปลี่ยนแปลงขั้นสุดท้ายและหลังจากแต่ละ ++ จะมีการสร้างและกำหนดให้กับ I ดังนั้นล็อคแข่งขันระหว่างสองเธรดจะแตกต่างกัน ดังนั้นจึงไม่ปลอดภัย
4. ThreadLocal และการวิเคราะห์รหัสที่มา
อาจไม่เหมาะสมที่จะพูดถึง Threadlocal ที่นี่ แต่ ThreadLocal เป็นวิธีการแทนที่ล็อค ดังนั้นจึงยังจำเป็นต้องพูดถึงมัน
แนวคิดพื้นฐานคือในหลายเธรดข้อมูลที่ขัดแย้งกันต้องถูกล็อค หากมีการใช้ ThreadLocal อินสแตนซ์ของวัตถุจะถูกจัดเตรียมไว้สำหรับแต่ละเธรด เธรดที่แตกต่างกันเข้าถึงวัตถุของตัวเองเท่านั้นไม่ใช่วัตถุอื่น ด้วยวิธีนี้ไม่จำเป็นต้องมีการล็อคอยู่
การทดสอบแพ็คเกจ; นำเข้า java.text.parseException; นำเข้า java.text.simpledateFormat; นำเข้า java.util.date; นำเข้า java.util.concurrent.executorservice; นำเข้า java.util.concurrent.executors; HH: MM: SS "); คลาสสแตติกระดับสาธารณะพากย์ใช้งานได้ {int i = 0; การแยกวิเคราะห์สาธารณะ (int i) {this.i = i; } โมฆะสาธารณะเรียกใช้ () {ลอง {วันที่ t = sdf.parse ("2016-02-16 17:00:" + i % 60); System.out.println (i + ":" + t); } catch (parseexception e) {e.printstacktrace (); }}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {executorService es = executors.newFixedThreadPool (10); สำหรับ (int i = 0; i <1000; i ++) {es.execute (ใหม่ parsedate (i)); - เนื่องจาก SimpledateFormat ไม่ปลอดภัยกับเธรดรหัสด้านบนจึงถูกใช้อย่างไม่ถูกต้อง วิธีที่ง่ายที่สุดคือการกำหนดชั้นเรียนด้วยตัวคุณเองและห่อด้วยการซิงโครไนซ์ (คล้ายกับคอลเลกชัน SynchronizedMap) สิ่งนี้จะทำให้เกิดปัญหาเมื่อทำพร้อมกันสูง การโต้แย้งเกี่ยวกับผลลัพธ์ที่ซิงโครไนซ์ในเธรดเพียงครั้งเดียวที่เข้ามาในแต่ละครั้งและปริมาณการเกิดพร้อมกันต่ำมาก
ปัญหานี้ได้รับการแก้ไขโดยใช้ Threadlocal เพื่อห่อหุ้ม SimpledateFormat
การทดสอบแพ็คเกจ; นำเข้า java.text.parseException; นำเข้า java.text.simpledateFormat; นำเข้า java.util.date; นำเข้า java.util.concurrent.executorservice; นำเข้า java.util.concurrent.executors; คลาสสแตติกระดับสาธารณะพากย์ใช้งานได้ {int i = 0; การแยกวิเคราะห์สาธารณะ (int i) {this.i = i; } โมฆะสาธารณะเรียกใช้ () {ลอง {if (tl.get () == null) {tl.set (ใหม่ simpledateFormat ("yyyy-mm-dd hh: mm: ss")); } วันที่ t = tl.get (). parse ("2016-02-16 17:00:" + i % 60); System.out.println (i + ":" + t); } catch (parseexception e) {e.printstacktrace (); }}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {executorService es = executors.newFixedThreadPool (10); สำหรับ (int i = 0; i <1000; i ++) {es.execute (ใหม่ parsedate (i)); -เมื่อแต่ละเธรดทำงานจะกำหนดว่าเธรดปัจจุบันมีวัตถุ SimpledateFormat หรือไม่
if (tl.get () == null)
ถ้าไม่ใหม่ SimpleDateFormat จะถูกผูกไว้กับเธรดปัจจุบัน
tl.set (ใหม่ simpledateFormat ("yyyy-mm-dd hh: mm: ss"));
จากนั้นใช้ SimpleDateFormat ของเธรดปัจจุบันเพื่อแยกวิเคราะห์
tl.get (). parse ("2016-02-16 17:00:" + i % 60);
ในรหัสเริ่มต้นมีเพียงหนึ่ง SimpledateFormat ซึ่งใช้ ThreadLocal และ SimpledateFormat นั้นใหม่สำหรับแต่ละเธรด
ควรสังเกตว่าคุณไม่ควรตั้งค่า public simpledateFormat ให้กับแต่ละ Threadlocal ที่นี่เนื่องจากไม่มีประโยชน์ แต่ละคนจำเป็นต้องได้รับใหม่ให้กับ SimpledateFormat
ในไฮเบอร์เนตมีแอพพลิเคชั่นทั่วไปสำหรับ ThreadLocal
ลองดูที่การใช้ซอร์สโค้ดการใช้งาน ThreadLocal
ก่อนอื่นมีตัวแปรสมาชิกในคลาสเธรด:
threadlocal.threadLocalMap threadLocals = null;
และแผนที่นี้เป็นกุญแจสำคัญในการใช้งาน ThreadLocal
ชุดโมฆะสาธารณะ (ค่า t) {เธรด t = เธรด currentthread (); threadlocalMap map = getMap (t); if (map! = null) map.set (นี้ค่า); else createMap (t, ค่า); - ตาม ThreadLocal คุณสามารถตั้งค่าและรับค่าที่สอดคล้องกัน
การใช้งาน ThreadLocalMap ที่นี่คล้ายกับ HASHMAP แต่มีความแตกต่างในการจัดการความขัดแย้งแฮช
เมื่อความขัดแย้งแฮชเกิดขึ้นใน ThreadLocalMap มันไม่เหมือน HASHMAP ที่จะใช้รายการที่เชื่อมโยงเพื่อแก้ไขความขัดแย้ง แต่จะวางดัชนี ++ ไว้ที่ดัชนีถัดไปเพื่อแก้ไขความขัดแย้ง