สารบัญ (?) [-]
หนึ่งขยายคลาส Javalangthread Two ใช้อินเตอร์เฟส javalangrunnable สามความแตกต่างระหว่างเธรดและการรันสี่สถานะการเปลี่ยนสถานะเธรดห้าการกำหนดตารางเวลาหกฟังก์ชั่นทั่วไปอธิบายวิธีการใช้วิธีการใช้วิธีการเข้าร่วม
บทความนี้ส่วนใหญ่พูดถึงวิธีการใช้งานของมัลติเธรดใน Java, การซิงโครไนซ์เธรด, การถ่ายโอนข้อมูลเธรด, สถานะเธรดและการใช้ฟังก์ชันเธรดที่สอดคล้องกันและภาพรวม
ก่อนอื่นมาพูดถึงความแตกต่างระหว่างกระบวนการและเธรด:
กระบวนการ: แต่ละกระบวนการมีรหัสอิสระและพื้นที่ข้อมูล (บริบทกระบวนการ) และการสลับระหว่างกระบวนการจะมีค่าใช้จ่ายขนาดใหญ่ กระบวนการมีเธรด 1-N
เธรด: เธรดประเภทเดียวกันรหัสแชร์และพื้นที่ข้อมูล แต่ละเธรดมีสแต็กที่ใช้งานอิสระและตัวนับโปรแกรม (PC) และค่าใช้จ่ายในการสลับเธรดมีขนาดเล็ก
เช่นเดียวกับกระบวนการเธรดแบ่งออกเป็นห้าขั้นตอน: การสร้าง, พร้อม, วิ่ง, การบล็อก, การปิดกั้นและการยกเลิก
Multi-Process หมายความว่าระบบปฏิบัติการสามารถทำงานได้หลายงาน (โปรแกรม) ในเวลาเดียวกัน
MultithReading หมายถึงสตรีมตามลำดับหลายรายการที่ดำเนินการในโปรแกรมเดียวกัน
ใน Java มีสองวิธีในการใช้มัลติเธรด หนึ่งคือการดำเนินการต่อคลาสเธรดต่อไปและอีกอันคือการใช้อินเทอร์เฟซที่สามารถเรียกใช้งานได้
1. ขยายคลาส java.lang.thread
แพ็คเกจ com.multithread.learning;/***@functon multithreading learning*@ผู้แต่ง Lin Bingwen*@time 2015.3.9*/คลาส Thread1 ขยายเธรด {ชื่อสตริงส่วนตัว; Public Thread1 (ชื่อสตริง) {this.name = name; } โมฆะสาธารณะเรียกใช้ () {สำหรับ (int i = 0; i <5; i ++) {system.out.println (ชื่อ + "เรียกใช้:" + i); ลอง {sleep ((int) math.random () * 10); } catch (interruptedException e) {e.printStackTrace (); }}}} คลาสสาธารณะหลัก {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {thread1 mth1 = thread1 ใหม่ ("a"); Thread1 MTH2 = ใหม่ Thread1 ("B"); mth1.start (); mth2.start (); - เอาท์พุท:
การวิ่ง: 0
B Run: 0
การวิ่ง: 1
การวิ่ง: 2
การวิ่ง: 3
การวิ่ง: 4
B Run: 1
B Run: 2
B Run: 3
B Run: 4
เรียกใช้อีกครั้ง:
การวิ่ง: 0
B Run: 0
B Run: 1
B Run: 2
B Run: 3
B Run: 4
การวิ่ง: 1
การวิ่ง: 2
การวิ่ง: 3
การวิ่ง: 4
ภาพประกอบ:
เมื่อโปรแกรมเริ่มต้นและเรียกใช้หลักเครื่องเสมือน Java จะเริ่มกระบวนการและเธรดหลักจะถูกสร้างขึ้นเมื่อเรียกว่า Main () ด้วยวิธีการเริ่มต้นของวัตถุทั้งสองของ Mitisay อีกสองเธรดก็เริ่มต้นขึ้นเช่นกันเพื่อให้แอปพลิเคชันทั้งหมดทำงานภายใต้หลายเธรด
หมายเหตุ: วิธีการเริ่มต้น () เรียกว่าไม่เรียกใช้รหัสหลายเธรดทันที แต่ทำให้เธรดกลายเป็นสถานะที่เรียกใช้ได้แทน เมื่อมันทำงานถูกกำหนดโดยระบบปฏิบัติการ
จากผลลัพธ์ของการทำงานของโปรแกรมเราสามารถค้นหาว่าโปรแกรมมัลติเธรดจะถูกดำเนินการตามลำดับ ดังนั้นรหัสที่ดำเนินการตามลำดับเท่านั้นจะต้องได้รับการออกแบบเป็นแบบมัลติเธรด
วัตถุประสงค์ของ Thread.SLEEP () การเรียกใช้วิธีการคือการป้องกันไม่ให้เธรดปัจจุบันครอบครองทรัพยากร CPU ที่ได้รับจากกระบวนการเพียงอย่างเดียวเพื่อที่จะปล่อยให้เวลาหนึ่งสำหรับเธรดอื่น ๆ เพื่อดำเนินการ
ในความเป็นจริงลำดับการดำเนินการของรหัสมัลติเธรดทั้งหมดไม่แน่นอนและผลลัพธ์ของการดำเนินการแต่ละครั้งจะสุ่ม
อย่างไรก็ตามหากวิธีการเริ่มต้นเรียกซ้ำ ๆ java.lang.illegalthreadstateException จะเกิดขึ้น
Thread1 MTH1 = ใหม่ Thread1 ("A"); Thread1 MTH2 = MTH1; mth1.start (); mth2.start (); เอาท์พุท:
ข้อยกเว้นในเธรด "หลัก" java.lang.illegalthreadstateException
ที่ java.lang.thread.start (ไม่ทราบแหล่งที่มา)
ที่ com.multithread.learning.main.main (main.java:31)
การวิ่ง: 0
การวิ่ง: 1
การวิ่ง: 2
การวิ่ง: 3
การวิ่ง: 4
2. ใช้อินเตอร์เฟส java.lang.runnable
/***@functon multithreading learning*@ผู้แต่ง Lin Bingwen*@Time 2015.3.9*/แพ็คเกจ com.multithread.runnable; คลาส Thread2 ใช้งาน runnable {ชื่อสตริงส่วนตัว; Public Thread2 (ชื่อสตริง) {this.name = name; } @Override โมฆะสาธารณะเรียกใช้ () {สำหรับ (int i = 0; i <5; i ++) {system.out.println (ชื่อ + "รัน:" + i); ลอง {thread.sleep ((int) math.random () * 10); } catch (interruptedException e) {e.printStackTrace (); }}}} คลาสสาธารณะหลัก {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {เธรดใหม่ (Thread2 ("C")). Start (); เธรดใหม่ (Thread2 ("D")). start (); - เอาท์พุท:
C Run: 0
D Run: 0
D Run: 1
C Run: 1
D Run: 2
C Run: 2
D Run: 3
C Run: 3
D Run: 4
C Run: 4
ภาพประกอบ:
คลาส Thread2 ใช้อินเทอร์เฟซ Runnable ซึ่งทำให้คลาสมีคุณสมบัติของคลาสมัลติเธรด วิธีการเรียกใช้ () เป็นแบบอนุสัญญาสำหรับโปรแกรมมัลติเธรด รหัสมัลติเธรดทั้งหมดอยู่ในวิธีการเรียกใช้ คลาสเธรดเป็นคลาสที่ใช้อินเทอร์เฟซที่เรียกใช้งานได้
เมื่อเริ่มต้นมัลติเธรดคุณจะต้องสร้างวัตถุก่อนผ่านเธรดตัวสร้างคลาสเธรด (เป้าหมายที่เรียกใช้ได้) จากนั้นเรียกใช้วิธีการเริ่มต้น () ของวัตถุเธรดเพื่อเรียกใช้รหัสมัลติเธรด
ในความเป็นจริงรหัสมัลติเธรดทั้งหมดทำงานโดยวิธีการเรียกใช้เธรดของ Start () ดังนั้นไม่ว่าจะเป็นการขยายคลาสเธรดหรือใช้อินเทอร์เฟซ Runnable เพื่อใช้งานมัลติเธรดหรือควบคุมเธรดในท้ายที่สุดผ่าน API วัตถุเธรดการทำความคุ้นเคยกับคลาสเธรด API เป็นพื้นฐานสำหรับการเขียนโปรแกรมแบบมัลติเธรด
3. ความแตกต่างระหว่างเธรดและ runnable
หากคลาสสืบทอดเธรดมันไม่เหมาะสำหรับการแบ่งปันทรัพยากร อย่างไรก็ตามหากมีการใช้งานอินเทอร์เฟซที่เรียกใช้งานได้มันเป็นเรื่องง่ายที่จะใช้การแบ่งปันทรัพยากร
แพ็คเกจ com.multithread.learning;/***@functon multi-thread learning, มรดกเธรด, ทรัพยากรไม่สามารถแชร์ได้*@ผู้แต่ง Lin Bingwen*@Time 2015.3.9*/class Thread1 ขยายเธรด {จำนวน int ส่วนตัว = 5; ชื่อสตริงส่วนตัว; Public Thread1 (ชื่อสตริง) {this.name = name; } โมฆะสาธารณะเรียกใช้ () {สำหรับ (int i = 0; i <5; i ++) {system.out.println (ชื่อ + "run count =" + count-); ลอง {sleep ((int) math.random () * 10); } catch (interruptedException e) {e.printStackTrace (); }}}} คลาสสาธารณะหลัก {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {thread1 mth1 = thread1 ใหม่ ("a"); Thread1 MTH2 = ใหม่ Thread1 ("B"); mth1.start (); mth2.start (); - เอาท์พุท:
B Run Count = 5
จำนวนรัน = 5
B Run Count = 4
B Run Count = 3
B Run Count = 2
B Run Count = 1
จำนวนรัน = 4
จำนวนรัน = 3
จำนวนรัน = 2
จำนวนรัน = 1
จากข้างต้นเราจะเห็นว่าการนับนั้นแตกต่างกันระหว่างเธรดที่แตกต่างกันซึ่งจะมีปัญหาใหญ่สำหรับระบบขายตั๋ว แน่นอนว่าการซิงโครไนซ์สามารถใช้งานได้ที่นี่ มาใช้ Runnable เพื่อทำที่นี่
/***@functon การเรียนรู้แบบมัลติเธรดมัลติแฉกสืบทอดการรันสามารถแชร์ทรัพยากรได้*@ผู้แต่ง Lin Bingwen*@Time 2015.3.9*/แพ็คเกจ com.multithread.runnable; คลาส Thread2 ดำเนินการ @Override โมฆะสาธารณะเรียกใช้ () {สำหรับ (int i = 0; i <5; i ++) {system.out.println (thread.currentthread (). getName () + "run count =" + count-); ลอง {thread.sleep ((int) math.random () * 10); } catch (interruptedException e) {e.printStackTrace (); }}}}} คลาสสาธารณะหลัก {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {thread2 my = new Thread2 (); เธรดใหม่ (My, "C"). start (); // mt เดียวกัน แต่มันเป็นไปไม่ได้ในเธรด หากคุณยกตัวอย่างวัตถุ MT ข้อยกเว้นจะปรากฏเธรดใหม่ (My, "D"). start (); เธรดใหม่ (My, "E") เริ่มต้น (); - เอาท์พุท:
C Run Count = 15
D run count = 14
e run count = 13
D run count = 12
D run count = 10
D run count = 9
D run count = 8
C Run Count = 11
e run count = 12
C Run Count = 7
e run count = 6
C Run Count = 5
e run count = 4
C Run Count = 3
e run count = 2
ที่นี่เราควรทราบว่าแต่ละเธรดใช้วัตถุอินสแตนซ์เดียวกัน หากไม่เหมือนกันเอฟเฟกต์จะเหมือนกับข้างต้น!
สรุป:
ข้อดีของการใช้อินเทอร์เฟซที่รันได้มากกว่าการสืบทอดคลาสเธรด:
1): เหมาะสำหรับหลายเธรดที่มีรหัสโปรแกรมเดียวกันเพื่อประมวลผลทรัพยากรเดียวกัน
2): สามารถหลีกเลี่ยงข้อ จำกัด ของการสืบทอดเดี่ยวในชวา
3): เพิ่มความทนทานของโปรแกรมรหัสสามารถใช้ร่วมกันได้หลายเธรดและรหัสและข้อมูลเป็นอิสระ
ให้ฉันเตือนคุณ: วิธีหลักคือเธรดจริง ใน Java เธรดจะเริ่มต้นในเวลาเดียวกัน สำหรับเวลาและใดที่ดำเนินการก่อนและใดก่อนมันขึ้นอยู่กับว่าใครได้รับทรัพยากร CPU ก่อน
ใน Java อย่างน้อย 2 เธรดจะเริ่มต้นทุกครั้งที่โปรแกรมทำงาน หนึ่งคือเธรดหลักและอีกอย่างคือเธรดคอลเลกชันขยะ เพราะเมื่อใดก็ตามที่คลาสถูกดำเนินการโดยใช้คำสั่ง Java JVM จะเริ่มต้นจริงและการฝึกงาน JVM แต่ละครั้งจะเริ่มกระบวนการในระบบปฏิบัติการ
4. การเปลี่ยนแปลงสถานะเธรด
1. สถานะใหม่ (ใหม่): วัตถุเธรดใหม่ถูกสร้างขึ้น
2. สถานะพร้อม (เรียกใช้): หลังจากสร้างวัตถุเธรดเธรดอื่น ๆ เรียกวิธีการเริ่มต้น () ของวัตถุ เธรดในสถานะนี้อยู่ในพูลเธรดที่รันได้และกลายเป็นรันได้รอรับสิทธิ์การใช้งาน CPU
3. สถานะการรัน: เธรดในสถานะพร้อมรับ CPU และดำเนินการรหัสโปรแกรม
4. สถานะที่ถูกบล็อก: สถานะที่ถูกบล็อกหมายความว่าเธรดจะให้สิทธิ์การใช้งาน CPU ด้วยเหตุผลบางอย่างและหยุดทำงานชั่วคราว มันไม่ได้จนกว่าเธรดจะเข้าสู่สถานะพร้อมที่จะมีโอกาสไปที่สถานะการวิ่ง การอุดตันมีสามประเภท:
(1) การรอบล็อก: เธรดที่รันจะดำเนินการวิธีการรอ () และ JVM จะใส่เธรดลงในพูลรอ
(2) การปิดกั้นแบบซิงโครนัส: เมื่อเธรดที่รันได้รับล็อคการซิงโครไนซ์ของวัตถุหากล็อคการซิงโครไนซ์ถูกครอบครองโดยเธรดอื่น JVM จะใส่เธรดลงในพูลล็อค
(iii), การบล็อกอื่น ๆ : เมื่อเธรดที่รันเรียกใช้งานวิธีการ sleep () หรือเข้าร่วม () หรือออกคำขอ I/O, JVM จะตั้งเธรดเป็นสถานะการบล็อก เมื่อสถานะการนอนหลับ () หมดเวลาเข้าร่วม () รอให้เธรดยุติหรือหมดเวลาหรือการประมวลผล I/O เสร็จสิ้นเธรดจะกลับเข้าสู่สถานะพร้อมอีกครั้ง
5. สถานะ Dead: เธรดเสร็จสิ้นการดำเนินการหรือออกจากวิธีการเรียกใช้ () เนื่องจากข้อยกเว้นและเธรดจะสิ้นสุดวัฏจักรชีวิต
5. การตั้งเวลาเธรด
การตั้งเวลาเธรด
1. ปรับลำดับความสำคัญของเธรด: กระทู้ Java มีลำดับความสำคัญและเธรดที่มีลำดับความสำคัญสูงจะได้รับโอกาสมากขึ้นในการทำงาน
ลำดับความสำคัญของเธรด Java จะถูกแทนด้วยจำนวนเต็มโดยมีช่วงค่า 1 ~ 10 คลาสเธรดมีค่าคงที่สามค่าต่อไปนี้:
max_priority คงที่
ลำดับความสำคัญสูงสุดที่เธรดสามารถมีได้คือ 10
int min_priority คงที่
ลำดับความสำคัญต่ำสุดที่เธรดสามารถมีได้คือ 1
int norm_priority แบบคงที่
ลำดับความสำคัญเริ่มต้นที่กำหนดให้กับเธรดคือ 5
วิธีการ setPriority () และ getPriority () ของคลาสเธรดใช้เพื่อตั้งค่าและรับลำดับความสำคัญของเธรดตามลำดับ
แต่ละเธรดมีลำดับความสำคัญเริ่มต้น ลำดับความสำคัญเริ่มต้นของเธรดหลักคือ thread.norm_priority
ลำดับความสำคัญของเธรดได้รับการสืบทอด ตัวอย่างเช่นหากเธรด B ถูกสร้างขึ้นในเธรด A ดังนั้น B จะมีลำดับความสำคัญเช่นเดียวกับ A.
JVM มีลำดับความสำคัญ 10 เธรด แต่มันไม่ได้แมปกับระบบปฏิบัติการทั่วไป หากคุณต้องการให้โปรแกรมถูกพอร์ตไปยังแต่ละระบบปฏิบัติการคุณควรใช้คลาสเธรดที่มีค่าคงที่สามค่าต่อไปนี้เป็นลำดับความสำคัญซึ่งสามารถมั่นใจได้ว่าลำดับความสำคัญเดียวกันใช้วิธีการกำหนดเวลาเดียวกัน
2. เมธอด Sleep: Thread.sleep (ยาวมิลลิส) เพื่อให้เธรดไปที่สถานะการบล็อก พารามิเตอร์ Millis ตั้งเวลาการนอนหลับเป็นมิลลิวินาที เมื่อการนอนหลับสิ้นสุดลงมันจะกลายเป็น แพลตฟอร์ม Sleep () มีการพกพาที่ดี
3. เธรดรอ: เมธอดรอ () ในคลาสวัตถุทำให้เธรดปัจจุบันรอจนกว่าเธรดอื่นจะเรียกวิธีการแจ้งเตือนของวัตถุ () หรือ notifyall () วิธีการปลุก วิธีการปลุกทั้งสองนี้เป็นวิธีการในคลาสวัตถุและพฤติกรรมของพวกเขาเทียบเท่ากับการโทรรอ (0)
4. การสัมปทานเธรด: Thread.yield () วิธีการระงับวัตถุเธรดที่ดำเนินการในปัจจุบันและให้โอกาสในการดำเนินการกับเธรดที่มีลำดับความสำคัญเท่ากันหรือสูงกว่า
5. เธรดเข้าร่วม: เข้าร่วม () วิธีการรอให้เธรดอื่นยุติ การเรียกวิธีการเข้าร่วม () ของเธรดอื่นในเธรดปัจจุบันเธรดปัจจุบันจะไปที่สถานะการบล็อกจนกว่ากระบวนการอื่นจะทำงานและเธรดปัจจุบันจะเปลี่ยนจากการปิดกั้นเป็นสถานะพร้อม
6. เธรด Wake-Up: วิธีการแจ้งเตือน () ในคลาสวัตถุจะตื่นขึ้นมาเธรดเดียวที่รออยู่ในการตรวจสอบวัตถุนี้ หากเธรดทั้งหมดกำลังรอวัตถุนี้หนึ่งในเธรดจะถูกเลือก ทางเลือกนั้นเป็นไปตามอำเภอใจและเกิดขึ้นเมื่อทำการตัดสินใจเกี่ยวกับการดำเนินการ เธรดรออยู่บนจอภาพของวัตถุโดยเรียกวิธีการรอคอยอย่างใดอย่างหนึ่ง ไม่สามารถดำเนินการเธรดที่ปลุกได้จนกว่าเธรดปัจจุบันจะยกเลิกการล็อคบนวัตถุนี้ เธรดที่ตื่นจะแข่งขันกับเธรดอื่น ๆ ทั้งหมดที่ซิงโครไนซ์อย่างแข็งขันกับวัตถุในลักษณะทั่วไป ตัวอย่างเช่นเธรดที่ตื่นไม่มีสิทธิ์ที่เชื่อถือได้หรือข้อเสียในการเป็นเธรดถัดไปที่ล็อควัตถุนี้ วิธีการที่คล้ายกันยังมี NotifyAll () ที่ปลุกเธรดทั้งหมดที่รออยู่ในการตรวจสอบวัตถุนี้
หมายเหตุ: ทั้งสองวิธีระงับ () และประวัติย่อ () ในเธรดได้ถูกยกเลิกใน JDK1.5 และจะไม่ถูกนำมาใช้อีก เพราะมีแนวโน้มที่จะหยุดชะงัก
6. คำอธิบายของฟังก์ชั่นทั่วไป
① Sleep (Long Millis): ปล่อยให้การนอนหลับของเธรดในปัจจุบันภายในจำนวนมิลลิวินาทีที่ระบุ (ระงับการดำเนินการ)
②join (): หมายถึงการรอให้เธรด t สิ้นสุด
วิธีใช้
เข้าร่วมเป็นวิธีการของคลาสเธรด มันถูกเรียกโดยตรงหลังจากเริ่มเธรด นั่นคือฟังก์ชั่นของการเข้าร่วม () คือ: "รอให้เธรดยุติ" สิ่งที่ต้องเข้าใจที่นี่คือเธรดหมายถึงเธรดหลักที่รอให้เธรดลูกสิ้นสุด นั่นคือรหัสหลังจากเธรดลูกเรียกวิธีการเข้าร่วม () และสามารถดำเนินการได้จนกว่าเธรดลูกจะเสร็จสิ้น
เธรด t = ใหม่ athread (); T.Start (); T.Join ();
ทำไมต้องใช้วิธีการเข้าร่วม ()
ในหลายกรณีเธรดหลักจะสร้างและเริ่มเธรดลูก หากจำเป็นต้องใช้เวลาจำนวนมากในการดำเนินการในเธรดลูกเธรดหลักมักจะสิ้นสุดก่อนที่จะมีด้ายลูก อย่างไรก็ตามหากเธรดหลักจำเป็นต้องใช้ผลการประมวลผลของเธรดลูกหลังจากการประมวลผลการทำธุรกรรมอื่น ๆ นั่นคือเธรดหลักจะต้องรอให้เธรดเด็กเสร็จสิ้นการดำเนินการก่อนที่จะสิ้นสุด ในเวลานี้ต้องใช้วิธีการเข้าร่วม ()
ไม่มีการเข้าร่วม /** *@functon multithreading การเรียนรู้, เข้าร่วม *@ผู้แต่ง Lin Bingwen *@Time 2015.3.9 */แพ็คเกจ com.multithread.join; คลาส Thread1 ขยายเธรด {ชื่อสตริงส่วนตัว; Public Thread1 (ชื่อสตริง) {super (ชื่อ); this.name = ชื่อ; } โมฆะสาธารณะเรียกใช้ () {system.out.println (thread.currentthread (). getName () + "เธรดเริ่มต้น!"); สำหรับ (int i = 0; i <5; i ++) {system.out.println ("subthread"+name+"run:"+i); ลอง {sleep ((int) math.random () * 10); } catch (interruptedException e) {e.printStackTrace (); }} system.out.println (thread.currentthread (). getName () + "เธรดรันสิ้นสุด!"); }} คลาสสาธารณะหลัก {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {system.out.println (thread.currentthread (). getName ()+"เธรดหลักเริ่มต้น!"); Thread1 MTH1 = ใหม่ Thread1 ("A"); Thread1 MTH2 = ใหม่ Thread1 ("B"); mth1.start (); mth2.start (); System.out.println (thread.currentthread (). getName ()+ "การรันเธรดหลักสิ้นสุด!"); - ผลลัพธ์ผลลัพธ์:
เธรดหลักหลักเริ่มทำงาน!
เธรดหลักหลักสิ้นสุดการทำงาน!
B การรันเธรดเริ่มต้นขึ้น!
ด้ายเด็ก B รัน: 0
การเรียกใช้เธรดเริ่มต้นขึ้น!
ด้ายเด็กวิ่ง: 0
ด้ายเด็ก B รัน: 1
ด้ายเด็กวิ่ง: 1
ด้ายเด็กวิ่ง: 2
ด้ายเด็กวิ่ง: 3
ด้ายเด็กวิ่ง: 4
การเรียกใช้ด้ายสิ้นสุด!
ด้ายเด็ก B รัน: 2
ด้ายเด็ก B รัน: 3
ด้ายเด็ก B รัน: 4
B เธรดวิ่งไป!
พบว่าเธรดหลักสิ้นสุดลงเร็วกว่าเธรดลูก
เข้าร่วม
คลาสสาธารณะหลัก {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {system.out.println (thread.currentthread (). getName ()+"เธรดหลักเริ่มต้น!"); Thread1 MTH1 = ใหม่ Thread1 ("A"); Thread1 MTH2 = ใหม่ Thread1 ("B"); mth1.start (); mth2.start (); ลอง {mth1.join (); } catch (interruptedException e) {e.printStackTrace (); } ลอง {mth2.join (); } catch (interruptedException e) {e.printStackTrace (); } system.out.println (thread.currentthread (). getName ()+ "การเรียกใช้เธรดหลักสิ้นสุด!"); - ผลการทำงาน:
เธรดหลักหลักเริ่มทำงาน!
การเรียกใช้เธรดเริ่มต้นขึ้น!
ด้ายเด็กวิ่ง: 0
B การรันเธรดเริ่มต้นขึ้น!
ด้ายเด็ก B รัน: 0
ด้ายเด็กวิ่ง: 1
ด้ายเด็ก B รัน: 1
ด้ายเด็กวิ่ง: 2
ด้ายเด็ก B รัน: 2
ด้ายเด็กวิ่ง: 3
ด้ายเด็ก B รัน: 3
ด้ายเด็กวิ่ง: 4
ด้ายเด็ก B รัน: 4
การเรียกใช้ด้ายสิ้นสุด!
เธรดหลักจะรออย่างแน่นอนจนกว่าเธรดลูกจะเสร็จก่อนที่จะสิ้นสุด
③yield (): หยุดการทำงานของวัตถุเธรดในปัจจุบันและดำเนินการเธรดอื่น ๆ
ฟังก์ชั่นของเมธอด thread.yield () คือ: หยุดชั่วคราวการดำเนินการในปัจจุบันการทำงานของวัตถุเธรดและดำเนินการเธรดอื่น ๆ
สิ่งที่อัตราผลตอบแทน () ควรทำคือการได้รับเธรดการรันปัจจุบันกลับไปยังสถานะ Runnable เพื่อให้เธรดอื่น ๆ ที่มีลำดับความสำคัญเดียวกันเพื่อให้ได้โอกาสในการทำงาน ดังนั้นวัตถุประสงค์ของการใช้ผลผลิต () คือการอนุญาตให้เธรดที่มีลำดับความสำคัญเดียวกันเพื่อดำเนินการอย่างเหมาะสม อย่างไรก็ตามในความเป็นจริงผลผลิต () ไม่สามารถรับประกันได้ว่าจะบรรลุวัตถุประสงค์ของสัมปทานเนื่องจากเธรดสัมปทานอาจถูกเลือกอีกครั้งโดยตัวกำหนดเวลาเธรด
สรุป: ผลผลิต () ไม่เคยทำให้เธรดไปที่สถานะรอ/นอนหลับ/การปิดกั้น ในกรณีส่วนใหญ่ผลผลิต () จะทำให้เธรดเปลี่ยนจากสถานะการทำงาน แต่อาจไม่ทำงาน คุณสามารถดูภาพด้านบน
/** *@functon มัลติเธรดการเรียนรู้ผลตอบแทน *@ผู้แต่ง Lin Bingwen *@Time 2015.3.9 */แพ็คเกจ com.multithread.yield; คลาส Threadyield ขยายเธรด {public Threadyield (ชื่อสตริง) {super (ชื่อ); } @Override โมฆะสาธารณะเรียกใช้ () {สำหรับ (int i = 1; i <= 50; i ++) {system.out.println ("" + this.getName () + "-----" + i); // เมื่อฉันอายุ 30 เธรดจะให้เวลา CPU และปล่อยให้เธรดอื่นหรือของตัวเองดำเนินการ (นั่นคือใครก็ตามที่คว้ามันก่อนที่จะดำเนินการ) ถ้า (i == 30) {this.yield (); }}}} คลาสสาธารณะหลัก {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {threatyield yt1 = ใหม่ Threadyield ("จางซาน"); Threadyield yt2 = new Threadyield ("Li Si"); yt1.start (); yt2.start (); - ผลการทำงาน:
กรณีแรก: Li Si (เธรด) จะหมดเวลา CPU เมื่อดำเนินการกับ 30 ในเวลานี้จางซาน (เธรด) จะคว้าเวลา CPU และดำเนินการ
สถานการณ์ที่สอง: เมื่อ Li Si (เธรด) ดำเนินการกับ 30 เวลา CPU จะถูกยกเลิก ในเวลานี้ Li Si (เธรด) คว้าเวลา CPU และดำเนินการ
ความแตกต่างระหว่างการนอนหลับ () และผลผลิต ()
ความแตกต่างระหว่างการนอนหลับ () และผลผลิต ()): การนอนหลับ () ทำให้เธรดปัจจุบันเข้าสู่สถานะนิ่งดังนั้นเธรดที่ดำเนินการ SLEEP () จะไม่ถูกดำเนินการภายในเวลาที่กำหนดอย่างแน่นอน ผลผลิต () เพียงแค่ทำให้เธรดปัจจุบันกลับไปที่สถานะการดำเนินการดังนั้นเธรดการดำเนินการให้ผลผลิต () อาจถูกเรียกใช้งานทันทีหลังจากป้อนสถานะการดำเนินการ
วิธีการนอนหลับทำให้เธรดวิ่งอยู่ในปัจจุบันเป็นระยะเวลาหนึ่งและเข้าสู่สถานะที่ไม่สามารถทำได้ ความยาวของช่วงเวลานี้ถูกกำหนดโดยโปรแกรม วิธีการให้ผลผลิตช่วยให้เธรดปัจจุบันให้ความเป็นเจ้าของ CPU แต่เวลาของการถ่ายโอนนั้นไม่สามารถพิสูจน์ได้ ในความเป็นจริงวิธีการให้ผลผลิต () สอดคล้องกับการดำเนินการต่อไปนี้: ก่อนอื่นตรวจสอบว่ามีเธรดที่มีลำดับความสำคัญเดียวกันในปัจจุบันอยู่ในสถานะที่สามารถเรียกใช้ได้เดียวกันหรือไม่ ถ้าเป็นเช่นนั้นส่งมอบความเป็นเจ้าของ CPU ให้กับเธรดนี้มิฉะนั้นจะดำเนินการต่อเธรดต้นฉบับต่อไป ดังนั้นวิธีการให้ผลผลิต () เรียกว่า "สัมปทาน" ซึ่งเปิดโอกาสให้การวิ่งไปยังเธรดอื่น ๆ ที่มีลำดับความสำคัญเดียวกัน
นอกจากนี้วิธีการนอนหลับยังช่วยให้เธรดลำดับความสำคัญต่ำกว่าได้รับโอกาสในการรัน แต่เมื่อมีการดำเนินการวิธีการให้ผลผลิต () เธรดปัจจุบันยังคงอยู่ในสถานะที่สามารถเรียกใช้ได้ดังนั้นจึงเป็นไปไม่ได้ที่จะยอมแพ้เธรดลำดับความสำคัญที่ต่ำกว่าเพื่อให้ได้เจ้าของ CPU ในภายหลัง ในระบบที่ทำงานหากเธรดลำดับความสำคัญที่สูงกว่าไม่เรียกวิธีการนอนหลับและไม่ถูกบล็อกโดย I/O ดังนั้นเธรดลำดับความสำคัญที่ต่ำกว่าสามารถรอให้เธรดลำดับความสำคัญสูงกว่าทั้งหมดวิ่งเพื่อให้มีโอกาสวิ่ง
④setPriority (): เปลี่ยนลำดับความสำคัญของเธรด
min_priority = 1
norm_priority = 5
max_priority = 10
การใช้งาน:
Thread4 T1 = ใหม่ Thread4 ("T1");
Thread4 t2 = ใหม่ Thread4 ("T2");
T1.SetPriority (thread.max_priority);
t2.setPriority (thread.min_priority);
⑤interrupt (): ขัดจังหวะเธรด วิธีการสิ้นสุดนี้ค่อนข้างหยาบ หากเธรด T เปิดทรัพยากรและไม่มีเวลาปิดนั่นคือวิธีการเรียกใช้จะถูกบังคับให้จบเธรดก่อนที่จะถูกดำเนินการซึ่งจะทำให้ทรัพยากรไม่สามารถปิดได้
วิธีที่ดีที่สุดในการยุติกระบวนการคือการใช้โปรแกรมตัวอย่างของฟังก์ชั่น Sleep () ตัวแปรบูลีนใช้ในคลาสเธรดเพื่อควบคุมเมื่อวิธีการเรียกใช้ () สิ้นสุดลง เมื่อวิธีการเรียกใช้ () สิ้นสุดเธรดจะสิ้นสุด
⑥ Wait ()
obj.wait () และ obj.notify () จะต้องใช้กับการซิงโครไนซ์ (OBJ) นั่นคือรอและแจ้งให้ทราบเกี่ยวกับการล็อค OBJ ที่ได้มา จากมุมมองที่ซิงโครไนซ์มันคือ obj.wait () และ obj.notify ต้องอยู่ในบล็อกคำสั่งซิงโครไนซ์ (obj) {... } จากมุมมองการทำงานให้รอหมายความว่าหลังจากที่เธรดได้รับการล็อควัตถุมันจะปล่อยล็อควัตถุอย่างแข็งขันและเธรดจะนอนหลับ ไม่สามารถรับการล็อควัตถุและการดำเนินการจะดำเนินการต่อไปจนกว่าเธรดอื่นจะเรียกวัตถุแจ้งให้ทราบ () เพื่อปลุกเธรด การแจ้งเตือนที่สอดคล้องกัน () คือการดำเนินการปลุกของล็อควัตถุ แต่สิ่งหนึ่งที่ควรทราบคือหลังจากการโทรแจ้ง () การล็อควัตถุจะไม่ถูกปล่อยออกมาทันที แต่การดำเนินการของบล็อกคำสั่งซิงโครไนซ์ที่สอดคล้องกัน () {} เสร็จสิ้นและล็อคจะถูกปล่อยออกมาโดยอัตโนมัติ JVM จะสุ่มเลือกเธรดจากเธรดการรอ () สิ่งนี้ให้การซิงโครไนซ์และการทำงานปลุกระหว่างเธรด ทั้งสอง tread.sleep () และ Object.wait () สามารถหยุดการควบคุมเธรดปัจจุบันและปล่อย CPU ความแตกต่างที่สำคัญคือในขณะที่ Object. Wait () ปล่อย CPU มันจะปล่อยการควบคุมของการล็อควัตถุ
ไม่เพียงพอที่จะเข้าใจแนวคิดและจำเป็นต้องได้รับการทดสอบในตัวอย่างที่เป็นประโยชน์เพื่อให้เข้าใจได้ดีขึ้น ตัวอย่างคลาสสิกที่สุดของการใช้งานของ Object.wait () และ Object.Notify () ควรเป็นปัญหาของการพิมพ์ ABC ด้วยสามเธรด นี่เป็นคำถามสัมภาษณ์ที่ค่อนข้างคลาสสิกและคำถามมีดังนี้:
สร้างสามเธรดเธรด A พิมพ์ 10 ครั้งเธรด b พิมพ์ b 10 ครั้ง, ด้าย c พิมพ์ C 10 ครั้ง, เธรด C ต้องการเธรดเพื่อทำงานในเวลาเดียวกันและ ABC ถูกพิมพ์สลับกัน 10 ครั้ง ปัญหานี้สามารถแก้ไขได้ง่ายโดยใช้การรอของวัตถุ () และแจ้ง () รหัสมีดังนี้:
/** * รอการใช้งาน * @author Dreasea * @time 2015.3.9 */แพ็คเกจ com.multithread.wait; คลาสสาธารณะ MyThreadPrinter2 ใช้งาน runnable {ชื่อสตริงส่วนตัว; วัตถุส่วนตัวก่อนหน้า; ตัวเองวัตถุส่วนตัว Private MyThreadPrinter2 (ชื่อสตริง, วัตถุก่อนหน้า, ตัวเองวัตถุ) {this.name = name; this.prev = prev; this.self = self; } @Override โมฆะสาธารณะเรียกใช้ () {int count = 10; ในขณะที่ (นับ> 0) {ซิงโครไนซ์ (ก่อนหน้า) {ซิงโครไนซ์ (ตนเอง) {system.out.print (ชื่อ); นับ--; self.notify (); } ลอง {prev.wait (); } catch (interruptedException e) {e.printStackTrace (); }}}} โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่นข้อยกเว้น {วัตถุ A = วัตถุใหม่ (); วัตถุ b = วัตถุใหม่ (); วัตถุ c = วัตถุใหม่ (); MyThreadPrinter2 PA = ใหม่ MyThreadPrinter2 ("A", C, A); mythreadprinter2 pb = ใหม่ mythreadprinter2 ("b", a, b); mythreadprinter2 pc = ใหม่ mythreadprinter2 ("c", b, c); เธรดใหม่ (Pa) .start (); Thread.sleep (100); // ตรวจสอบให้แน่ใจว่าได้ดำเนินการเธรดใหม่ (pb). start (); Thread.sleep (100); - ผลลัพธ์ผลลัพธ์:
abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc
ก่อนอื่นเรามาอธิบายแนวคิดโดยรวม จากมุมมองทั่วไปปัญหานี้เป็นการดำเนินการปลุกแบบซิงโครนัสระหว่างสามเธรด วัตถุประสงค์หลักคือการดำเนินการสามเธรดใน threada-> threadb-> threadc-> threada loop ในการควบคุมลำดับของการดำเนินการเธรดคำสั่งของการปลุกและการรอจะต้องได้รับการพิจารณาดังนั้นแต่ละเธรดจะต้องถือล็อควัตถุสองตัวในเวลาเดียวกันก่อนที่จะสามารถดำเนินการต่อได้ ล็อควัตถุคือก่อนหน้าซึ่งเป็นล็อควัตถุที่จัดขึ้นโดยเธรดก่อนหน้า อีกอันคือล็อคของวัตถุ แนวคิดหลักคือในการควบคุมลำดับของการดำเนินการคุณต้องถือการล็อคก่อนหน้านั่นคือเธรดก่อนหน้าจะต้องปล่อยล็อควัตถุของตัวเองจากนั้นใช้สำหรับล็อควัตถุของตัวเอง พิมพ์เมื่อทั้งคู่เป็นทั้งคู่ จากนั้นโทร Self.Notify () ก่อนที่จะปล่อยล็อควัตถุของตัวเองปลุกเธรดรอถัดไปจากนั้นโทร prev.wait () เพื่อปล่อยล็อควัตถุก่อนหน้าปิดเธรดปัจจุบันและรอให้ลูปถูกปลุกอีกครั้ง เรียกใช้รหัสด้านบนและคุณจะพบว่าสามเธรดพิมพ์ ABC ในลูปรวม 10 ครั้ง กระบวนการหลักของการรันโปรแกรมคือเธรด A เป็นครั้งแรกที่เรียกใช้ถือล็อควัตถุของ C และ A แล้วปล่อยล็อคของ A และ C และตื่นขึ้น B. Thread B รอล็อค A แล้วใช้สำหรับล็อค B จากนั้นพิมพ์ B ปัญหา แต่ถ้าคุณคิดอย่างรอบคอบคุณจะพบว่ามีปัญหาซึ่งเป็นเงื่อนไขเริ่มต้น สามเธรดเริ่มต้นขึ้นตามลำดับของ A, B และ C ตามความคิดก่อนหน้านี้ตื่นขึ้น B, B ปลุก C, C และจากนั้นตื่นขึ้นมา A. อย่างไรก็ตามสมมติฐานนี้ขึ้นอยู่กับลำดับของการกำหนดเวลาเธรดและการดำเนินการใน JVM
ความแตกต่างระหว่างการรอและนอนหลับ
จุดทั่วไป:
1. พวกเขาทั้งหมดอยู่ในสภาพแวดล้อมแบบมัลติเธรดและสามารถบล็อกจำนวนมิลลิวินาทีที่ระบุที่การโทรและส่งคืนโปรแกรม
2. ทั้งการรอ () และการนอนหลับ () สามารถขัดจังหวะสถานะหยุดของเธรดผ่านวิธีการขัดจังหวะ () เพื่อให้เธรดโยนการขัดจังหวะการรับรู้ทันที
หากเธรด A ต้องการจบเธรด B ทันทีวิธีการขัดจังหวะสามารถเรียกได้ในอินสแตนซ์เธรดที่สอดคล้องกับเธรด B หากเธรด B กำลังรอ/นอนหลับ/เข้าร่วมในขณะนี้เธรด B จะโยน InterruptedException ทันทีและส่งคืนโดยตรงใน catch () {} เพื่อจบเธรดอย่างปลอดภัย
ควรสังเกตว่าการขัดจังหวะการรับรู้ถูกโยนโดยด้ายจากภายในไม่ใช่โดยวิธีการขัดจังหวะ () เมื่อ interrupt () ถูกเรียกใช้ในเธรดหากเธรดกำลังเรียกใช้รหัสปกติเธรดจะไม่โยน InterruptedException เลย อย่างไรก็ตามเมื่อเธรดเข้าสู่การรอ ()/sleep ()/เข้าร่วม () การขัดจังหวะจะถูกโยนทันที
ความแตกต่าง:
1. เมธอดคลาสเธรด: sleep (), retial () ฯลฯ
วิธีการวัตถุ: รอ () และแจ้ง () ฯลฯ
2. แต่ละวัตถุมีล็อคเพื่อควบคุมการเข้าถึงแบบซิงโครนัส คำหลักที่ซิงโครไนซ์สามารถโต้ตอบกับล็อคของวัตถุเพื่อรับรู้การซิงโครไนซ์เธรด
วิธีการนอนหลับไม่ปล่อยล็อคในขณะที่วิธีการรอจะปล่อยล็อคเพื่อให้เธรดอื่นสามารถใช้บล็อกหรือวิธีการควบคุมแบบซิงโครนัส
3. รอการแจ้งเตือนและแจ้งเตือนสามารถใช้ในวิธีการควบคุมการซิงโครไนซ์หรือบล็อกควบคุมการซิงโครไนซ์เท่านั้นในขณะที่การนอนหลับสามารถใช้งานได้ทุกที่
4. การนอนหลับต้องจับข้อยกเว้นในขณะที่รอแจ้งและแจ้งเตือนไม่จำเป็นต้องจับข้อยกเว้นดังนั้นความแตกต่างที่ใหญ่ที่สุดระหว่างการนอนหลับ () และวิธีการรอ () คือ:
เมื่อนอนหลับ () นอนหลับให้ล็อควัตถุและยังคงมีล็อคอยู่
เมื่อรอ () นอนหลับวัตถุจะถูกปล่อยออกมา
อย่างไรก็ตามรอ () และการนอนหลับ () สามารถขัดจังหวะสถานะหยุดของเธรดผ่านวิธีการขัดจังหวะ () เพื่อให้เธรดโยนการขัดจังหวะการรับรู้ทันที (แต่ไม่แนะนำให้ใช้วิธีนี้)
วิธีการนอนหลับ ()
Sleep () ทำให้เธรดปัจจุบันเข้าสู่สถานะนิ่ง (บล็อกเธรดปัจจุบัน) ให้ใช้ถ้วยและวัตถุประสงค์คือเพื่อป้องกันไม่ให้เธรดปัจจุบันครอบครองทรัพยากร CPU ที่ได้รับจากกระบวนการเพียงอย่างเดียว
sleep () เป็นวิธีการคงที่ของคลาสเธรด ดังนั้นจึงไม่สามารถเปลี่ยนล็อคเครื่องของวัตถุได้ดังนั้นเมื่อเรียกใช้เมธอด Sleep () ในบล็อกที่ซิงโครไนซ์แม้ว่าเธรดจะอยู่เฉยๆ แต่การล็อคเครื่องของวัตถุจะไม่ถูกปล่อยออกมาและเธรดอื่น ๆ ไม่สามารถเข้าถึงวัตถุได้ (แม้ว่ามันจะยังคงล็อควัตถุแม้ว่ามันจะหลับ)
หลังจากการนอนหลับ () การนอนหลับ () เวลานอนหลับหมดอายุเธรดไม่จำเป็นต้องดำเนินการทันทีเนื่องจากเธรดอื่นอาจกำลังทำงานอยู่และไม่ได้กำหนดให้ละทิ้งการดำเนินการเว้นแต่เธรดจะมีลำดับความสำคัญสูงกว่า
รอ () วิธีการ
วิธีการรอ () เป็นวิธีการในคลาสวัตถุ เมื่อเธรดดำเนินการวิธีการรอ () มันจะเข้าสู่พูลรอที่เกี่ยวข้องกับวัตถุและในเวลาเดียวกันจะสูญเสีย (ปล่อย) การล็อคเครื่องของวัตถุ (สูญเสียการล็อคเครื่องชั่วคราวและเวลาหมดเวลารอ (ใช้เวลานาน) จะต้องส่งคืนวัตถุ); เธรดอื่น ๆ สามารถเข้าถึงได้
Wait () ใช้การแจ้งเตือนหรือแจ้งเตือนหรือเวลานอนที่ระบุเพื่อปลุกเธรดในพูลรออยู่ปัจจุบัน
Wiat () จะต้องอยู่ในบล็อกที่ซิงโครไนซ์มิฉะนั้นจะมีข้อยกเว้น "java.lang.illegalmonitorstateException" จะถูกโยนลงเมื่อโปรแกรมรันไทม์
7. คำอธิบายข้อกำหนดของเธรดทั่วไป
เธรดหลัก: เธรดที่สร้างโดยโปรแกรมการเรียก JVM Main ()
เธรดปัจจุบัน: นี่เป็นแนวคิดที่สับสน โดยทั่วไปหมายถึงกระบวนการที่ได้รับผ่าน tread.currentthread ()
เธรดพื้นหลัง: หมายถึงเธรดที่ให้บริการกับเธรดอื่น ๆ หรือที่เรียกว่าเธรด daemon เธรดคอลเลกชัน JVM Garbage เป็นเธรดพื้นหลัง ความแตกต่างระหว่างเธรดผู้ใช้และเธรด daemon คือการรอให้เธรดหลักสิ้นสุดเธรดเบื้องหน้าขึ้นอยู่กับส่วนท้ายของเธรดหลัก: มันหมายถึงเธรดที่ยอมรับบริการของเธรดพื้นหลัง ในความเป็นจริงเธรดพื้นหลังเบื้องหน้าเชื่อมต่อเข้าด้วยกันเช่นเดียวกับความสัมพันธ์ระหว่างหุ่นเชิดและผู้ดูแลเบื้องหลัง หุ่นเชิดเป็นด้ายเบื้องหน้าและผู้ดูแลเบื้องหลังเป็นด้ายพื้นหลัง เธรดที่สร้างขึ้นโดยเธรดเบื้องหน้ายังเป็นเธรดเบื้องหน้าโดยค่าเริ่มต้น คุณสามารถใช้วิธี isdaemon () และ setdaemon () เพื่อกำหนดและตั้งค่าว่าเธรดเป็นเธรดพื้นหลังหรือไม่
วิธีการทั่วไปบางอย่างของคลาสเธรด:
sleep (): บังคับให้ด้ายนอนใน n มิลลิวินาที
isalive (): กำหนดว่าเธรดมีชีวิตอยู่หรือไม่
เข้าร่วม (): รอเธรดเพื่อยุติ
ActivEcount (): จำนวนเธรดที่ใช้งานอยู่ในโปรแกรม
enumerate (): ระบุเธรดในโปรแกรม
CurrentThread (): รับเธรดปัจจุบัน
ISDAEMON (): ด้ายเป็นด้ายด้ายหรือไม่
setdaemon (): ตั้งเธรดเป็นเธรด daemon (ความแตกต่างระหว่างเธรดผู้ใช้และเธรด daemon คือการรอให้เธรดหลักสิ้นสุดขึ้นอยู่กับส่วนท้ายของเธรดหลัก)
setName (): ตั้งชื่อสำหรับเธรด
รอ (): บังคับให้เธรดรอ
แจ้งเตือน (): แจ้งเธรดเพื่อดำเนินการต่อ
SetPriority (): ตั้งค่าลำดับความสำคัญของเธรด
8. การซิงโครไนซ์เธรด
1. คำหลักที่ซิงโครไนซ์มีสองขอบเขต:
1) อยู่ในอินสแตนซ์ของวัตถุ amethod ที่ซิงโครไนซ์ () {} สามารถป้องกันหลายเธรดจากการเข้าถึงวิธีการซิงโครไนซ์ของวัตถุนี้ในเวลาเดียวกัน (ถ้าวัตถุมีวิธีการซิงโครไนซ์หลายวิธีตราบใดที่เธรดหนึ่งเข้าถึงหนึ่งในวิธีการซิงโครไนซ์เธรดอื่น ๆ ไม่สามารถเข้าถึงวิธีการซิงโครไนซ์ใด ๆ ในวัตถุในเวลาเดียวกัน) At this time, the synchronized method of different object instances is uninterrupted. That is to say, other threads can still access the synchronized method in another object instance of the same class at the same time;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?还得对synchronized关键字的作用进行深入了解才可定论。
总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
接着来讨论synchronized用到不同地方对代码产生的影响:
假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。
1. 把synchronized当作函数修饰符时,示例代码如下:
Public synchronized void methodAAA(){//….}这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。
上边的示例代码等同于如下代码:
public void methodAAA(){synchronized (this) // (1){ //…..}}(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
2.同步块,示例代码如下:
public void method3(SomeObject so) { synchronized(so){ //…..}}这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable{ private byte[] lock = new byte[0]; // 特殊的instance变量Public void methodA(){ synchronized(lock) { //… }}//…..}注:零长度的byte数组对象创建起来将比任何对象都经济查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:
Class Foo{public synchronized static void methodAAA() // 同步的static 函数{//….}public void methodBBB(){ synchronized(Foo.class) // class literal(类名称字面常量)} }代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将Foo.class和P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
九、线程数据传递
在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。
9.1、通过构造方法传递数据在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:
package mythread; public class MyThread1 extends Thread { private String name; public MyThread1(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { Thread thread = new MyThread1("world"); thread.start(); -由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。
9.2、通过变量和方法传递数据
向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置name变量:
package mythread; public class MyThread2 implements Runnable { private String name; public void setName(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { MyThread2 myThread = new MyThread2(); myThread.setName("world"); Thread thread = new Thread(myThread); thread.start(); - 9.3、通过回调函数传递数据
上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个value是无法事先就传入线程类的。
package mythread; class Data { public int value = 0; } class Work { public void process(Data data, Integer numbers) { for (int n : numbers) { data.value += n; } } } public class MyThread3 extends Thread { private Work work; public MyThread3(Work work) { this.work = work; } public void run() { java.util.Random random = new java.util.Random(); Data data = new Data(); int n1 = random.nextInt(1000); int n2 = random.nextInt(2000); int n3 = random.nextInt(3000); work.process(data, n1, n2, n3); // Use the callback function System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+" + String.valueOf(n3) + "=" + data.value); } public static void main(String[] args) { Thread thread = new MyThread3(new Work()); thread.start(); -以上就是对Java 多线程的详解,希望能帮助你学习这部分知识,谢谢大家对本站的支持!