เธรดเป็นหน่วยพื้นฐานของการทำงานของระบบปฏิบัติการ มันถูกห่อหุ้มไว้ในกระบวนการ แม้ว่าเราจะไม่สร้างเธรดด้วยตนเอง กระบวนการนี้จะมีเธรดเริ่มต้นทำงานอยู่
สำหรับ JVM เมื่อเราเขียนโปรแกรมแบบเธรดเดียวเพื่อรัน จะมีอย่างน้อยสองเธรดที่ทำงานอยู่ใน JVM อันหนึ่งคือโปรแกรมที่เราสร้างขึ้น และอีกอันคือการรวบรวมขยะ
ข้อมูลพื้นฐานของเธรด
เราสามารถรับข้อมูลบางอย่างเกี่ยวกับเธรดปัจจุบันผ่านเมธอด Thread.currentThread() และแก้ไขได้
ลองดูรหัสต่อไปนี้:
Thread.currentThread().setName("ทดสอบ");
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
ชื่อ = Thread.currentThread().getName();
ลำดับความสำคัญ = Thread.currentThread().getPriority();
groupName = Thread.currentThread().getThreadGroup().getName();
isDaemon = Thread.currentThread().isDaemon();
System.out.println("ชื่อเธรด:" + ชื่อ);
System.out.println("ลำดับความสำคัญ:" + ลำดับความสำคัญ);
GroupName แต่ละเธรดจะอยู่ในกลุ่มเธรดตามค่าเริ่มต้น เรายังสามารถสร้างกลุ่มเธรดได้อย่างชัดเจน นอกจากนี้ กลุ่มเธรดยังสามารถมีกลุ่มเธรดย่อยได้ ดังนั้นเธรดและกลุ่มเธรดจึงสร้างโครงสร้างแบบต้นไม้
Name แต่ละเธรดจะมีชื่อ หากไม่ได้ระบุไว้อย่างชัดเจน กฎการตั้งชื่อคือ "Thread-xxx"
Priority แต่ละเธรดจะมีลำดับความสำคัญของตัวเอง และวิธีการจัดการลำดับความสำคัญของ JVM คือ "ยึดเอาเสียก่อน" เมื่อ JVM ค้นหาเธรดที่มีลำดับความสำคัญสูง JVM จะทำการโพลเธรดนั้นทันที สำหรับหลายเธรดที่มีลำดับความสำคัญเท่ากัน ลำดับความสำคัญของเธรดของ Java มีตั้งแต่ 1 ถึง 10 โดยค่าเริ่มต้นคือ 5 คลาส Thread กำหนดค่าคงที่สองค่า: MIN_PRIORITY และ MAX_PRIORITY เพื่อแสดงลำดับความสำคัญสูงสุดและต่ำสุด
เราสามารถดูโค้ดต่อไปนี้ ซึ่งกำหนดสองเธรดที่มีลำดับความสำคัญต่างกัน:
เธรด thread2 = เธรดใหม่ ("สูง")
-
การรันโมฆะสาธารณะ ()
-
สำหรับ (int i = 0; i < 5; i++)
-
System.out.println("เธรด 2 กำลังทำงานอยู่");
-
-
-
thread1.setPriority(เธรด.MIN_PRIORITY);
thread2.setPriority(เธรด.MAX_PRIORITY);
thread1.start();
thread2.start();
-
thread1.start();
-
วิธีการสร้างเธรด
เนื้อหาข้างต้นทั้งหมดแสดงให้เห็นข้อมูลบางอย่างในเธรดเริ่มต้น ดังนั้นจะสร้างเธรดได้อย่างไร? ใน Java เรามี 3 วิธีในการสร้างเธรด
เธรดใน Java สืบทอดคลาส Thread หรือใช้อินเทอร์เฟซ Runnable มาดูทีละรายการกัน
ใช้คลาสภายในเพื่อสร้างเธรด
เราสามารถใช้คลาสภายในเพื่อสร้างเธรด กระบวนการคือการประกาศตัวแปรประเภทเธรดและแทนที่วิธีการรัน รหัสตัวอย่างจะเป็นดังนี้:
เราสามารถรับคลาสจาก Thread และแทนที่วิธีการรันของมันในลักษณะเดียวกันกับข้างต้น รหัสตัวอย่างจะเป็นดังนี้:
โมฆะคงสาธารณะ createThreadBySubClass()
-
เธรด MyThread = MyThread ใหม่ ();
เธรด.เริ่มต้น();
-
เราสามารถกำหนดคลาสเพื่อใช้อินเทอร์เฟซ Runnable จากนั้นใช้อินสแตนซ์ของคลาสนี้เป็นพารามิเตอร์เพื่อสร้างตัวสร้างตัวแปร Thread รหัสตัวอย่างจะเป็นดังนี้:
โมฆะคงที่สาธารณะ createThreadByRunnable()
-
MyRunnable runnable = ใหม่ MyRunnable();
เธรดเธรด = เธรดใหม่ (รันได้);
เธรด.เริ่มต้น();
-
สิ่งนี้เกี่ยวข้องกับโหมดการทำงานแบบมัลติเธรดใน Java สำหรับ Java เมื่อทำงานแบบมัลติเธรดจะมีความแตกต่างระหว่าง "มัลติเธรดแบบหลายวัตถุ" และ "มัลติเธรดแบบวัตถุเดียว":
Multi-object multi-threading โปรแกรมจะสร้างวัตถุเธรดหลายตัวในระหว่างการรัน และหนึ่งเธรดทำงานในแต่ละวัตถุ
วัตถุเดี่ยวแบบมัลติเธรด โปรแกรมจะสร้างวัตถุเธรดในระหว่างการรันและรันหลายเธรดบนวัตถุนั้น
แน่นอนว่าจากมุมมองการซิงโครไนซ์เธรดและการจัดกำหนดการ มัลติเธรดแบบหลายอ็อบเจ็กต์นั้นง่ายกว่า จากวิธีการสร้างเธรดสามวิธีข้างต้น สองวิธีแรกคือ "multi-object multi-thread" และวิธีที่สามสามารถใช้ "multi-object multi-thread" หรือ "single-object single-thread"
ลองดูโค้ดตัวอย่างต่อไปนี้ ซึ่งจะใช้วิธีการ Object.notify วิธีนี้จะปลุกเธรดบนวัตถุ และวิธี Object.notifyAll จะปลุกเธรดทั้งหมดบนวัตถุ
โมฆะคงสาธารณะ main (String [] args) พ่น InterruptedException
-
แจ้งทดสอบ();
แจ้งTest2();
แจ้งTest3();
-
โมฆะคงที่ส่วนตัว notifyTest() พ่น InterruptedException
-
MyThread[] arrThreads = MyThread ใหม่ [3];
สำหรับ (int i = 0; i < arrThreads.length; i++)
-
arrThreads[i] = MyThread ใหม่();
arrThreads[i].id = ฉัน;
arrThreads[i].setDaemon(จริง);
arrThreads[i].เริ่มต้น();
-
เธรด.สลีป(500);
สำหรับ (int i = 0; i < arrThreads.length; i++)
-
ซิงโครไนซ์ (arrThreads [i])
-
arrThreads[i].แจ้ง();
-
-
-
โมฆะคงที่ส่วนตัว notifyTest2 () พ่น InterruptedException
-
MyRunner[] arrMyRunners = MyRunner ใหม่ [3];
เธรด [] arrThreads = เธรดใหม่ [3];
สำหรับ (int i = 0; i < arrThreads.length; i++)
-
arrMyRunners[i] = MyRunner ใหม่();
arrMyRunners[i].id = ฉัน;
arrThreads[i] = เธรดใหม่ (arrMyRunners[i]);
arrThreads[i].setDaemon(จริง);
arrThreads[i].เริ่มต้น();
-
เธรด.สลีป(500);
สำหรับ (int i = 0; i < arrMyRunners.length; i++)
-
ซิงโครไนซ์ (arrMyRunners [i])
-
arrMyRunners[i].แจ้งเตือน();
-
-
-
โมฆะคงที่ส่วนตัว notifyTest3 () พ่น InterruptedException
-
MyRunner runner = MyRunner ใหม่();
เธรด [] arrThreads = เธรดใหม่ [3];
สำหรับ (int i = 0; i < arrThreads.length; i++)
-
arrThreads[i] = เธรดใหม่ (รองชนะเลิศ);
arrThreads[i].setDaemon(จริง);
arrThreads[i].เริ่มต้น();
-
เธรด.สลีป(500);
ซิงโครไนซ์ (นักวิ่ง)
-
runner.notifyAll();
-
-
-
คลาส MyThread ขยายเธรด
-
รหัสประจำตัวสาธารณะ = 0;
การรันโมฆะสาธารณะ ()
-
System.out.println("Thread " + id + " พร้อมที่จะเข้าสู่โหมดสลีปเป็นเวลา 5 นาที");
พยายาม
-
ซิงโครไนซ์ (นี้)
-
นี่.รอ(5*60*1,000);
-
-
จับ (InterruptedException เช่น)
-
เช่น printStackTrace();
-
System.out.println("The "th" + id + "thread was wakened.");
-
-
คลาส MyRunner ใช้งาน Runnable
-
รหัสสาธารณะ = 0;
การรันโมฆะสาธารณะ ()
-
System.out.println("Thread " + id + " พร้อมที่จะเข้าสู่โหมดสลีปเป็นเวลา 5 นาที");
พยายาม
-
ซิงโครไนซ์ (นี้)
-
นี่.รอ(5*60*1,000);
-
-
จับ (InterruptedException เช่น)
-
เช่น printStackTrace();
-
System.out.println("The "th" + id + "thread was wakened.");
-
-
วิธีการ notifyAll เหมาะสำหรับสถานการณ์ "วัตถุเดียวหลายเธรด" เนื่องจากวิธีการแจ้งเตือนจะสุ่มปลุกเธรดเดียวบนวัตถุเท่านั้น
การสลับสถานะเธรด
สำหรับเธรด ตั้งแต่เวลาที่เราสร้างจนถึงเธรดสิ้นสุด สถานะของเธรดในระหว่างกระบวนการนี้อาจเป็นดังนี้:
การสร้าง: มีอินสแตนซ์ Thread อยู่แล้ว แต่ CPU ยังคงมีทรัพยากรและการแบ่งส่วนเวลาที่จัดสรรไว้
พร้อม: เธรดได้รับทรัพยากรทั้งหมดที่จำเป็นในการทำงานและกำลังรอให้ CPU กำหนดเวลา
ทำงานอยู่: เธรดอยู่ในส่วนแบ่งเวลาของ CPU ปัจจุบัน และกำลังดำเนินการตรรกะที่เกี่ยวข้อง
สลีป: โดยทั่วไปสถานะหลังจากเรียก Thread.sleep ในขณะนี้ เธรดยังคงมีทรัพยากรต่างๆ ที่จำเป็นสำหรับการดำเนินการ แต่จะไม่ได้รับการกำหนดเวลาโดย CPU
ระงับ: โดยทั่วไปสถานะหลังจากการเรียก Thread.suspend CPU จะไม่กำหนดเวลาเธรด
ความตาย: เธรดสิ้นสุดหรือเรียกว่าเมธอด Thread.stop
ต่อไปเราจะสาธิตวิธีการเปลี่ยนสถานะเธรด ก่อนอื่นเราจะใช้วิธีการต่อไปนี้:
Thread() หรือ Thread(Runnable): สร้างเธรด
Thread.start: เริ่มเธรด
Thread.sleep: สลับเธรดเป็นสถานะสลีป
Thread.interrupt: ขัดจังหวะการดำเนินการของเธรด
Thread.join: รอให้เธรดสิ้นสุด
Thread.yield: กีดกันเธรดของส่วนแบ่งเวลาดำเนินการบน CPU และรอกำหนดการถัดไป
Object.wait: ล็อคเธรดทั้งหมดบน Object และจะไม่ทำงานต่อไปจนกว่าจะมีวิธีการแจ้งเตือน
Object.notify: สุ่มปลุกเธรดบน Object
Object.notifyAll: ปลุกเธรดทั้งหมดบน Object
ถึงเวลาสาธิตแล้ว! - -
กระทู้รอและตื่น
ส่วนใหญ่จะใช้เมธอด Object.wait และ Object.notify ที่นี่ โปรดดูตัวอย่างการแจ้งเตือนด้านบน ควรสังเกตว่าทั้งรอและแจ้งเตือนต้องกำหนดเป้าหมายวัตถุเดียวกัน เมื่อเราสร้างเธรดโดยใช้อินเทอร์เฟซ Runnable เราควรใช้ทั้งสองวิธีนี้กับวัตถุ Runnable แทนวัตถุ Thread
กระทู้หลับแล้วตื่นเลย
โมฆะคงสาธารณะ main (String [] args) พ่น InterruptedException
-
ทดสอบการนอนหลับ();
-
โมฆะส่วนตัว sleepTest() พ่น InterruptedException
-
เธรดเธรด = เธรดใหม่ ()
-
การรันโมฆะสาธารณะ ()
-
System.out.println("Thread" + Thread.currentThread().getName() + "มันจะเข้าสู่โหมดสลีปเป็นเวลา 5 นาที");
พยายาม
-
ด้าย.สลีป(5*60*1,000);
-
จับ (InterruptedException เช่น)
-
System.out.println("Thread" + Thread.currentThread().getName() + "สลีปถูกขัดจังหวะ");
-
System.out.println("Thread" + Thread.currentThread().getName() + "สิ้นสุดโหมดสลีป");
-
-
thread.setDaemon(จริง);
เธรด.เริ่มต้น();
เธรด.สลีป(500);
เธรด.ขัดจังหวะ();
-
-
แม้ว่าจะมีวิธี Thread.stop แต่ไม่แนะนำให้ใช้วิธีนี้ เราสามารถใช้กลไกการนอนหลับและการปลุกด้านบนเพื่อให้เธรดสิ้นสุดเธรดเมื่อประมวลผล IterruptedException
โมฆะคงสาธารณะ main (String [] args) พ่น InterruptedException
-
หยุดการทดสอบ();
-
โมฆะส่วนตัวแบบคงที่ stopTest() พ่น InterruptedException
-
เธรดเธรด = เธรดใหม่ ()
-
การรันโมฆะสาธารณะ ()
-
System.out.println("เธรดกำลังทำงานอยู่");
พยายาม
-
ด้าย.สลีป(1*60*1,000);
-
จับ (InterruptedException เช่น)
-
System.out.println("ขัดจังหวะเธรด, สิ้นสุดเธรด");
กลับ;
-
System.out.println("เธรดสิ้นสุดตามปกติ");
-
-
เธรด.เริ่มต้น();
เธรด.สลีป(500);
เธรด.ขัดจังหวะ();
-
-
เมื่อเราสร้างเธรดย่อย 10 เธรดในเธรดหลัก และจากนั้นเราคาดว่าหลังจากเธรดย่อยทั้ง 10 เธรดเสร็จสมบูรณ์ เธรดหลักจะดำเนินการตรรกะถัดไป ในเวลานี้ ถึงเวลาที่ Thread.join จะปรากฏขึ้น
โมฆะคงสาธารณะ main (String [] args) พ่น InterruptedException
-
เข้าร่วมทดสอบ();
-
โมฆะคงที่ส่วนตัว joinTest() พ่น InterruptedException
-
เธรดเธรด = เธรดใหม่ ()
-
การรันโมฆะสาธารณะ ()
-
พยายาม
-
สำหรับ(int i = 0; i < 5; i++)
-
System.out.println("เธรดกำลังทำงานอยู่");
เธรด.สลีป(1,000);
-
-
จับ (InterruptedException เช่น)
-
เช่น printStackTrace();
-
-
-
thread.setDaemon(จริง);
เธรด.เริ่มต้น();
เธรด.สลีป(1,000);
เธรด.เข้าร่วม();
System.out.println("เธรดหลักสิ้นสุดตามปกติ");
-
-
เรารู้ว่าเธรดทั้งหมดภายใต้กระบวนการใช้พื้นที่หน่วยความจำร่วมกัน แล้วเราจะถ่ายโอนข้อความระหว่างเธรดที่ต่างกันได้อย่างไร เมื่อตรวจสอบ Java I/O เราได้พูดคุยเกี่ยวกับ PipedStream และ PipedReader และนี่คือที่มาของสิ่งเหล่านี้
สองตัวอย่างต่อไปนี้มีฟังก์ชันที่เหมือนกันทุกประการ ข้อแตกต่างคือตัวอย่างหนึ่งใช้ Stream และอีกตัวอย่างหนึ่งใช้ Reader/Writer
เธรด thread1 = เธรดใหม่ ()
-
การรันโมฆะสาธารณะ ()
-
BufferedReader br = BufferedReader ใหม่ (InputStreamReader ใหม่ (System.in));
พยายาม
-
ในขณะที่(จริง)
-
ข้อความสตริง = br.readLine();
pos.write(message.getBytes());
ถ้า (message.equals("end")) พัง;
-
br.ปิด();
pos.ปิด();
-
จับ (ข้อยกเว้นเช่น)
-
เช่น printStackTrace();
-
-
-
เธรด thread2 = เธรดใหม่ ()
-
การรันโมฆะสาธารณะ ()
-
ไบต์ [] บัฟเฟอร์ = ไบต์ใหม่ [1024];
int ไบต์อ่าน = 0;
พยายาม
-
ในขณะที่ ((bytesRead = pis.read (บัฟเฟอร์, 0, buffer.length)) != -1)
-
System.out.println (สตริงใหม่ (บัฟเฟอร์));
if (new String(buffer).equals("end")) แตก;
บัฟเฟอร์ = โมฆะ;
บัฟเฟอร์ = ไบต์ใหม่ [1024];
-
pis.ปิด();
บัฟเฟอร์ = โมฆะ;
-
จับ (ข้อยกเว้นเช่น)
-
เช่น printStackTrace();
-
-
-
thread1.setDaemon(จริง);
thread2.setDaemon(จริง);
thread1.start();
thread2.start();
thread1.join();
thread2.เข้าร่วม();
-
เธรด thread1 = เธรดใหม่ ()
-
การรันโมฆะสาธารณะ ()
-
BufferedReader br = BufferedReader ใหม่ (InputStreamReader ใหม่ (System.in));
พยายาม
-
ในขณะที่(จริง)
-
ข้อความสตริง = br.readLine();
bw.write(ข้อความ);
bw.newLine();
bw.ฟลัช();
ถ้า (message.equals("end")) พัง;
-
br.ปิด();
pw.ปิด();
bw.ปิด();
-
จับ (ข้อยกเว้นเช่น)
-
เช่น printStackTrace();
-
-
-
เธรด thread2 = เธรดใหม่ ()
-
การรันโมฆะสาธารณะ ()
-
เส้นสตริง = null;
พยายาม
-
ในขณะที่ ((line = br.readLine()) != null)
-
System.out.println (บรรทัด);
ถ้า (line.equals("end")) แตก;
-
br.ปิด();
pr.ปิด();
-
จับ (ข้อยกเว้นเช่น)
-
เช่น printStackTrace();
-
-
-
thread1.setDaemon(จริง);
thread2.setDaemon(จริง);
thread1.start();
thread2.start();
thread1.join();
thread2.เข้าร่วม();
-