คำถามสัมภาษณ์ Java multithreading
กระบวนการคือสภาพแวดล้อมการทำงานที่มีในตัวเอง ซึ่งสามารถถือเป็นโปรแกรมหรือแอปพลิเคชันได้ เธรดเป็นงานที่ดำเนินการในกระบวนการ สภาพแวดล้อมรันไทม์ Java เป็นกระบวนการเดียวที่มีคลาสและโปรแกรมที่แตกต่างกัน เธรดสามารถเรียกได้ว่าเป็นกระบวนการที่มีน้ำหนักเบา เธรดต้องการทรัพยากรน้อยลงในการสร้างและอยู่ในกระบวนการ และสามารถแบ่งปันทรัพยากรภายในกระบวนการได้
ในโปรแกรมแบบมัลติเธรด เธรดหลายรายการจะถูกดำเนินการพร้อมกันเพื่อปรับปรุงประสิทธิภาพของโปรแกรม CPU จะไม่เข้าสู่สถานะว่างเนื่องจากเธรดต้องรอทรัพยากร เธรดจำนวนมากใช้หน่วยความจำฮีปร่วมกัน ดังนั้นจึงเป็นการดีกว่าที่จะสร้างหลายเธรดเพื่อทำงานบางอย่างมากกว่าการสร้างหลายกระบวนการ ตัวอย่างเช่น Servlets ดีกว่า CGI เพราะ Servlets รองรับการทำงานแบบมัลติเธรด ในขณะที่ CGI ไม่รองรับ
เมื่อเราสร้างเธรดในโปรแกรม Java จะเรียกว่าเธรดผู้ใช้ เธรด daemon คือเธรดที่ดำเนินการในเบื้องหลัง และไม่ได้ป้องกัน JVM ไม่ให้ยุติการทำงาน เมื่อไม่มีการรันเธรดผู้ใช้ JVM จะปิดโปรแกรมและออกจากโปรแกรม เธรดชายด์ที่สร้างโดยเธรด daemon ยังคงเป็นเธรด daemon
มีสองวิธีในการสร้างเธรด วิธีหนึ่งคือการใช้อินเทอร์เฟซ Runnable จากนั้นส่งต่อไปยังตัวสร้างเธรดเพื่อสร้างอ็อบเจ็กต์เธรด อีกวิธีหนึ่งคือการสืบทอดคลาสเธรดโดยตรง หากคุณต้องการทราบข้อมูลเพิ่มเติม คุณสามารถอ่านบทความนี้เกี่ยวกับวิธีสร้างเธรดใน Java
เมื่อเราสร้างเธรดใหม่ในโปรแกรม Java สถานะของเธรดจะเป็นใหม่ เมื่อเราเรียกใช้เมธอด start() ของเธรด สถานะจะเปลี่ยนเป็น Runnable ตัวกำหนดเวลาเธรดจัดสรรเวลา CPU ให้กับเธรดในพูลเธรดที่รันได้ และเปลี่ยนสถานะเป็นกำลังทำงาน สถานะของเธรดอื่นๆ ได้แก่ กำลังรอ บล็อค และ ตาย อ่านบทความนี้เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับวงจรการใช้งานของเธรด
แน่นอน แต่ถ้าเราเรียกเมธอด run() ของ Thread มันจะทำงานเหมือนเมธอดปกติ ในการรันโค้ดของเราในเธรดใหม่ เราต้องใช้เมธอด Thread.start()
เราสามารถใช้เมธอด Sleep() ของคลาส Thread เพื่อหยุดเธรดชั่วคราวได้ช่วงระยะเวลาหนึ่ง ควรสังเกตว่าการดำเนินการนี้ไม่ได้ยุติเธรด เมื่อเธรดถูกปลุกจากโหมดสลีป สถานะของเธรดจะเปลี่ยนเป็น Runnable และจะถูกดำเนินการตามกำหนดการของเธรด
แต่ละเธรดมีลำดับความสำคัญ โดยทั่วไปแล้ว เธรดที่มีลำดับความสำคัญสูงจะมีลำดับความสำคัญเมื่อรัน แต่ขึ้นอยู่กับการดำเนินการตามกำหนดเวลาเธรด ซึ่งขึ้นอยู่กับระบบปฏิบัติการ เราสามารถกำหนดลำดับความสำคัญของเธรดได้ แต่ไม่ได้รับประกันว่าเธรดที่มีลำดับความสำคัญสูงจะดำเนินการก่อนเธรดที่มีลำดับความสำคัญต่ำ ลำดับความสำคัญของเธรดเป็นตัวแปร int (ตั้งแต่ 1-10), 1 หมายถึงลำดับความสำคัญต่ำสุด, 10 หมายถึงลำดับความสำคัญสูงสุด
ตัวกำหนดเวลาเธรดเป็นบริการระบบปฏิบัติการที่รับผิดชอบในการจัดสรรเวลา CPU ให้กับเธรดในสถานะ Runnable เมื่อเราสร้างเธรดและเริ่มต้นแล้ว การดำเนินการจะขึ้นอยู่กับการใช้งานตัวกำหนดเวลาเธรด การแบ่งเวลาหมายถึงกระบวนการจัดสรรเวลา CPU ที่มีอยู่ให้กับเธรดที่รันได้ที่มีอยู่ การจัดสรรเวลา CPU อาจขึ้นอยู่กับลำดับความสำคัญของเธรดหรือเวลาที่เธรดรอ การกำหนดเวลาเธรดไม่ได้ถูกควบคุมโดยเครื่องเสมือน Java ดังนั้นจึงเป็นการดีกว่าที่แอปพลิเคชันจะควบคุม (นั่นคือ อย่าทำให้โปรแกรมของคุณขึ้นอยู่กับลำดับความสำคัญของเธรด)
การสลับบริบทเป็นกระบวนการจัดเก็บและกู้คืนสถานะ CPU ซึ่งช่วยให้การประมวลผลเธรดสามารถดำเนินการต่อจากจุดที่หยุดชะงักได้ การสลับบริบทเป็นคุณสมบัติที่สำคัญของระบบปฏิบัติการมัลติทาสกิ้งและสภาพแวดล้อมแบบมัลติเธรด
เราสามารถใช้เมธอด joint() ของคลาส Thread เพื่อให้แน่ใจว่าเธรดทั้งหมดที่สร้างโดยโปรแกรมจะสิ้นสุดก่อนที่เมธอด main() จะออก นี่คือบทความเกี่ยวกับวิธีการร่วม () ของคลาสเธรด
เมื่อทรัพยากรสามารถแบ่งปันระหว่างเธรดได้ การสื่อสารระหว่างเธรดจึงเป็นวิธีการสำคัญในการประสานงานกับเธรดเหล่านั้น wait()/notify()/notifyAll() วิธีการในคลาส Object สามารถใช้เพื่อสื่อสารระหว่างเธรดเกี่ยวกับสถานะของการล็อคทรัพยากร คลิกที่นี่เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการรอเธรด แจ้งเตือน และแจ้งเตือนทั้งหมด
แต่ละอ็อบเจ็กต์ใน Java มีการล็อค (มอนิเตอร์ ซึ่งสามารถเป็นมอนิเตอร์ได้) และวิธีการเช่น wait() และ notify() ใช้เพื่อรอการล็อคของอ็อบเจ็กต์หรือแจ้งเธรดอื่น ๆ ว่ามอนิเตอร์ของอ็อบเจ็กต์พร้อมใช้งาน ไม่มีการล็อกหรือซิงโครไนซ์สำหรับอ็อบเจ็กต์ใดๆ ในเธรด Java นั่นเป็นเหตุผลว่าทำไมวิธีการเหล่านี้จึงเป็นส่วนหนึ่งของคลาส Object ดังนั้นทุกคลาสใน Java จึงมีวิธีการพื้นฐานสำหรับการสื่อสารระหว่างเธรด
เมื่อเธรดจำเป็นต้องเรียกใช้เมธอด wait() ของอ็อบเจ็กต์ เธรดจะต้องเป็นเจ้าของการล็อกของอ็อบเจ็กต์ จากนั้นเธรดจะปล่อยการล็อกอ็อบเจ็กต์และเข้าสู่สถานะรอจนกว่าเธรดอื่นจะเรียกใช้เมธอด notify() บนอ็อบเจ็กต์ ในทำนองเดียวกัน เมื่อเธรดจำเป็นต้องเรียกใช้เมธอด notify() ของอ็อบเจ็กต์ มันจะปล่อยการล็อกของอ็อบเจ็กต์ เพื่อให้เธรดที่รออื่นสามารถรับการล็อกอ็อบเจ็กต์ได้ เนื่องจากวิธีการเหล่านี้ทั้งหมดต้องการเธรดเพื่อเก็บล็อคของอ็อบเจ็กต์ ซึ่งสามารถทำได้ผ่านการซิงโครไนซ์เท่านั้น จึงสามารถเรียกได้ในวิธีการซิงโครไนซ์หรือบล็อกซิงโครไนซ์เท่านั้น
เมธอด sleep() และ Yield() ของคลาส Thread จะทำงานบนเธรดที่กำลังดำเนินการอยู่ ดังนั้นจึงไม่สมเหตุสมผลที่จะเรียกเมธอดเหล่านี้กับเธรดอื่นที่กำลังรออยู่ นั่นเป็นสาเหตุที่วิธีการเหล่านี้คงที่ พวกเขาสามารถทำงานในเธรดที่กำลังดำเนินการอยู่และป้องกันไม่ให้โปรแกรมเมอร์คิดผิดว่าวิธีการเหล่านี้สามารถเรียกใช้ในเธรดอื่นที่ไม่ได้ทำงานอยู่
มีหลายวิธีในการรับรองความปลอดภัยของเธรดใน Java - การซิงโครไนซ์ การใช้คลาสอะตอมมิกที่เกิดขึ้นพร้อมกัน การใช้การล็อคที่เกิดขึ้นพร้อมกัน การใช้คีย์เวิร์ดระเหย การใช้คลาสที่ไม่เปลี่ยนรูป และคลาสที่ปลอดภัยของเธรด คุณสามารถเรียนรู้เพิ่มเติมได้ในบทช่วยสอนเรื่องความปลอดภัยของเธรด
เมื่อเราใช้คีย์เวิร์ด volatile เพื่อแก้ไขตัวแปร เธรดจะอ่านตัวแปรโดยตรงและไม่แคชมัน เพื่อให้แน่ใจว่าตัวแปรที่อ่านโดยเธรดนั้นสอดคล้องกับตัวแปรในหน่วยความจำ
บล็อกที่ซิงโครไนซ์เป็นตัวเลือกที่ดีกว่าเนื่องจากไม่ได้ล็อควัตถุทั้งหมด (แน่นอน คุณสามารถทำให้มันล็อควัตถุทั้งหมดได้) วิธีการซิงโครไนซ์จะล็อคออบเจ็กต์ทั้งหมด แม้ว่าจะมีบล็อกซิงโครไนซ์ที่ไม่เกี่ยวข้องหลายบล็อกในคลาส ซึ่งมักจะทำให้พวกเขาหยุดดำเนินการและต้องรอเพื่อรับการล็อคบนออบเจ็กต์
เธรดสามารถตั้งค่าเป็นเธรด daemon ได้โดยใช้เมธอด setDaemon(true) ของคลาส Thread ควรสังเกตว่าจำเป็นต้องเรียกเมธอดนี้ก่อนที่จะเรียกใช้เมธอด start() มิฉะนั้น IllegalThreadStateException จะถูกโยนทิ้ง
ThreadLocal ใช้เพื่อสร้างตัวแปรภายในเธรด เรารู้ว่าเธรดทั้งหมดของออบเจ็กต์จะแชร์ตัวแปรส่วนกลาง ดังนั้นตัวแปรเหล่านี้จึงไม่ปลอดภัยสำหรับเธรด แต่เมื่อเราไม่ต้องการใช้การซิงโครไนซ์ เราสามารถเลือกตัวแปร ThreadLocal ได้
แต่ละเธรดจะมีตัวแปรเธรดของตัวเอง และพวกเขาสามารถใช้เมธอด get()/set() เพื่อรับค่าเริ่มต้นหรือเปลี่ยนค่าภายในเธรดได้ โดยทั่วไปอินสแตนซ์ ThreadLocal ต้องการให้สถานะเธรดที่เกี่ยวข้องเป็นคุณสมบัติคงที่ส่วนตัว ในบทความตัวอย่าง ThreadLocal คุณสามารถดูโปรแกรมขนาดเล็กเกี่ยวกับ ThreadLocal
ThreadGroup เป็นคลาสที่มีวัตถุประสงค์เพื่อให้ข้อมูลเกี่ยวกับกลุ่มเธรด
ThreadGroup API ค่อนข้างอ่อนแอและไม่มีฟังก์ชันมากกว่า Thread มีสองฟังก์ชันหลัก: ฟังก์ชันแรกคือการรับรายการเธรดที่ใช้งานอยู่ในกลุ่มเธรด ฟังก์ชันอื่นคือการตั้งค่าตัวจัดการข้อยกเว้นที่ไม่ถูกตรวจจับ (ตัวจัดการข้อยกเว้น ncaught) สำหรับเธรด อย่างไรก็ตาม ใน Java 1.5 คลาส Thread ยังเพิ่มเมธอด setUncaughtExceptionHandler(UncaughtExceptionHandler eh) ดังนั้น ThreadGroup จึงล้าสมัยและไม่แนะนำให้ใช้ต่อไป
t1.setUncaughtExceptionHandler(UncaughtExceptionHandler ใหม่(){ @Overridepublic void uncaughtException(Thread t, Throwable e) {System.out.println("ข้อยกเว้นเกิดขึ้น:"+e.getMessage());} });
เธรดดัมพ์คือรายการเธรดที่ใช้งาน JVM ซึ่งมีประโยชน์มากสำหรับการวิเคราะห์คอขวดและการหยุดชะงักของระบบ มีหลายวิธีในการรับเธรดดัมพ์ - โดยใช้ Profiler, คำสั่ง Kill -3, เครื่องมือ jstack ฯลฯ ฉันชอบเครื่องมือ jstack เพราะมันใช้งานง่ายและมาพร้อมกับ JDK เนื่องจากเป็นเครื่องมือที่ใช้เทอร์มินัล เราจึงสามารถเขียนสคริปต์บางตัวเพื่อสร้างเธรดดัมพ์เป็นระยะเพื่อการวิเคราะห์ได้ อ่านเอกสารนี้เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับการสร้างดัมพ์เธรด
การหยุดชะงักหมายถึงสถานการณ์ที่มากกว่าสองเธรดถูกบล็อกอย่างถาวร สถานการณ์นี้ต้องการอย่างน้อยสองเธรดและทรัพยากรมากกว่าสองรายการ
ในการวิเคราะห์การหยุดชะงัก เราต้องดูเธรดดัมพ์ของแอปพลิเคชัน Java เราจำเป็นต้องค้นหาว่าเธรดใดที่อยู่ในสถานะ BLOCKED และทรัพยากรที่เธรดเหล่านั้นกำลังรออยู่ ทรัพยากรแต่ละรายการมีรหัสที่ไม่ซ้ำกัน โดยใช้รหัสนี้เราจะสามารถค้นหาว่าเธรดใดเป็นเจ้าของการล็อคอ็อบเจ็กต์อยู่แล้ว
การหลีกเลี่ยงการล็อกแบบซ้อน การใช้การล็อกเฉพาะเมื่อจำเป็น และการหลีกเลี่ยงการรอแบบไม่มีกำหนดเป็นวิธีทั่วไปในการหลีกเลี่ยงการหยุดชะงัก อ่านบทความนี้เพื่อเรียนรู้วิธีวิเคราะห์การหยุดชะงัก
java.util.Timer เป็นคลาสเครื่องมือที่สามารถใช้เพื่อกำหนดเวลาเธรดเพื่อดำเนินการในเวลาที่กำหนดในอนาคต คลาส Timer สามารถใช้เพื่อกำหนดเวลางานแบบครั้งเดียวหรืองานตามกำหนดเวลาได้
java.util.TimerTask เป็นคลาสนามธรรมที่ใช้อินเทอร์เฟซ Runnable เราจำเป็นต้องสืบทอดคลาสนี้เพื่อสร้างงานที่กำหนดเวลาไว้ของเราเอง และใช้ Timer เพื่อกำหนดเวลาการดำเนินการ
นี่คือตัวอย่างเกี่ยวกับ Java Timer
เธรดพูลจัดการกลุ่มของเธรดของผู้ปฏิบัติงานและยังมีคิวสำหรับวางงานที่รอดำเนินการอีกด้วย
java.util.concurrent.Executors จัดเตรียมการใช้งานอินเทอร์เฟซ java.util.concurrent.Executor สำหรับการสร้างเธรดพูล ตัวอย่าง Thread Pool แสดงวิธีการสร้างและใช้งาน Thread Pool หรืออ่านตัวอย่าง ScheduledThreadPoolExecutor เพื่อเรียนรู้วิธีสร้างงานเป็นระยะ
คำถามสัมภาษณ์พร้อมกันของ Java
การดำเนินงานแบบอะตอมมิกหมายถึงหน่วยงานการดำเนินงานที่ไม่ได้รับผลกระทบจากการดำเนินงานอื่นๆ การดำเนินการแบบอะตอมมิกเป็นวิธีที่จำเป็นเพื่อหลีกเลี่ยงความไม่สอดคล้องกันของข้อมูลในสภาพแวดล้อมแบบมัลติเธรด
int++ ไม่ใช่การดำเนินการแบบอะตอมมิก ดังนั้นเมื่อเธรดหนึ่งอ่านค่าของมันและเพิ่ม 1 เธรดอื่นอาจอ่านค่าก่อนหน้า ซึ่งจะทำให้เกิดข้อผิดพลาด
เพื่อที่จะแก้ไขปัญหานี้ เราต้องแน่ใจว่าการดำเนินการเพิ่มเป็นแบบอะตอมมิก ก่อน JDK1.5 เราอาจใช้เทคโนโลยีการซิงโครไนซ์เพื่อทำสิ่งนี้ ตั้งแต่ JDK 1.5 เป็นต้นไป แพ็กเกจ java.util.concurrent.atomic จัดเตรียมคลาสการโหลดประเภท int และ long ที่รับประกันโดยอัตโนมัติว่าการดำเนินการเป็นแบบอะตอมมิก และไม่จำเป็นต้องใช้การซิงโครไนซ์ คุณสามารถอ่านบทความนี้เพื่อเรียนรู้เกี่ยวกับคลาสอะตอมมิกของ Java
อินเทอร์เฟซการล็อคให้การดำเนินการล็อคที่ปรับขนาดได้มากกว่าวิธีการซิงโครไนซ์และบล็อกที่ซิงโครไนซ์ ช่วยให้มีโครงสร้างที่ยืดหยุ่นมากขึ้นซึ่งอาจมีคุณสมบัติที่แตกต่างกันโดยสิ้นเชิง และสามารถรองรับคลาสต่างๆ ที่เกี่ยวข้องของออบเจ็กต์ที่มีเงื่อนไขได้
ข้อดีของมันคือ:
อ่านเพิ่มเติมเกี่ยวกับตัวอย่างการล็อค
กรอบงาน Executor เปิดตัวใน Java 5 พร้อมด้วยอินเทอร์เฟซ java.util.concurrent.Executor กรอบงานผู้ดำเนินการคือกรอบงานสำหรับงานอะซิงโครนัสที่ถูกเรียก กำหนดเวลา ดำเนินการ และควบคุมตามชุดกลยุทธ์การดำเนินการ
การสร้างเธรดไม่จำกัดอาจทำให้หน่วยความจำของแอปพลิเคชันล้นได้ ดังนั้นการสร้างกลุ่มเธรดจึงเป็นวิธีแก้ปัญหาที่ดีกว่า เนื่องจากสามารถจำกัดจำนวนเธรดได้ และเธรดเหล่านี้สามารถรีไซเคิลและนำกลับมาใช้ใหม่ได้ สะดวกมากในการสร้างเธรดพูลโดยใช้เฟรมเวิร์กของ Executor อ่านบทความนี้เพื่อเรียนรู้วิธีสร้างเธรดพูลโดยใช้เฟรมเวิร์กของ Executor
คุณลักษณะของ java.util.concurrent.BlockingQueue คือ เมื่อคิวว่าง การดำเนินการรับหรือลบองค์ประกอบออกจากคิวจะถูกบล็อก หรือเมื่อคิวเต็ม การดำเนินการเพิ่มองค์ประกอบในคิวจะถูกบล็อก .
คิวการบล็อคไม่ยอมรับค่า Null เมื่อคุณพยายามที่จะเพิ่มค่า Null ให้กับคิว นั้นจะเกิด NullPointerException
การบล็อกการใช้งานคิวนั้นปลอดภัยสำหรับเธรด และวิธีการสืบค้นทั้งหมดเป็นแบบอะตอมมิก และใช้การล็อคภายในหรือการควบคุมการทำงานพร้อมกันในรูปแบบอื่น
อินเทอร์เฟซ BlockingQueue เป็นส่วนหนึ่งของเฟรมเวิร์กคอลเลกชัน Java และส่วนใหญ่จะใช้เพื่อใช้ปัญหาของผู้ผลิตและผู้บริโภค
อ่านบทความนี้เพื่อเรียนรู้วิธีการนำปัญหาระหว่างผู้ผลิตและผู้บริโภคไปใช้โดยใช้การบล็อกคิว
Java 5 เปิดตัวอินเทอร์เฟซ java.util.concurrent.Callable ในแพ็คเกจการทำงานพร้อมกันซึ่งคล้ายกับอินเทอร์เฟซ Runnable มาก แต่สามารถส่งคืนอ็อบเจ็กต์หรือส่งข้อยกเว้นได้
อินเทอร์เฟซ Callable ใช้ข้อมูลทั่วไปเพื่อกำหนดประเภทการส่งคืน คลาส Executors จัดเตรียมวิธีการที่เป็นประโยชน์บางอย่างเพื่อดำเนินงานภายใน Callable ในเธรดพูล เนื่องจากงาน Callable เป็นแบบขนาน เราจึงต้องรอผลลัพธ์ที่ส่งคืน วัตถุ java.util.concurrent.Future แก้ปัญหานี้ให้เรา หลังจากที่เธรดพูลส่งงาน Callable แล้ว อ็อบเจ็กต์ Future จะถูกส่งคืน เมื่อใช้มัน เราจะสามารถทราบสถานะของงาน Callable และรับผลการดำเนินการที่ส่งคืนโดย Callable Future จัดเตรียมเมธอด get() เพื่อให้เราสามารถรอให้ Callable สิ้นสุดและรับผลการดำเนินการ
อ่านบทความนี้เพื่อทราบตัวอย่างเพิ่มเติมเกี่ยวกับ Callable อนาคต
FutureTask เป็นการใช้งานพื้นฐานของ Future ซึ่งเราสามารถใช้กับ Executors เพื่อประมวลผลงานแบบอะซิงโครนัส โดยปกติแล้วเราไม่จำเป็นต้องใช้คลาส FutureTask แต่จะมีประโยชน์มากเมื่อเราวางแผนที่จะแทนที่วิธีการบางอย่างของอินเทอร์เฟซ Future และคงการใช้งานพื้นฐานดั้งเดิมไว้ เราสามารถสืบทอดจากมันและแทนที่วิธีการที่เราต้องการได้ อ่านตัวอย่าง Java FutureTask เพื่อเรียนรู้วิธีใช้งาน
คลาสคอลเลกชั่น Java ทำงานเร็วเมื่อล้มเหลว ซึ่งหมายความว่าเมื่อมีการแก้ไขคอลเลกชั่นและเธรดใช้ตัววนซ้ำเพื่อสำรวจคอลเลกชั่น เมธอด next() ของตัววนซ้ำจะส่งข้อยกเว้น ConcurrentModificationException
คอนเทนเนอร์ที่เกิดขึ้นพร้อมกันรองรับการแวะผ่านและการอัพเดตพร้อมกัน
คลาสหลักคือ ConcurrentHashMap, CopyOnWriteArrayList และ CopyOnWriteArraySet อ่านบทความนี้เพื่อเรียนรู้วิธีหลีกเลี่ยง ConcurrentModificationException
ผู้ดำเนินการจัดเตรียมวิธีการอรรถประโยชน์บางอย่างสำหรับคลาส Executor, ExecutorService, ScheduledExecutorService, ThreadFactory และ Callable
ผู้ดำเนินการสามารถใช้เพื่อสร้างพูลเธรดได้อย่างง่ายดาย
ข้อความต้นฉบับ: Journaldev.com การแปล: ifeve ผู้แปล: Zheng Xudong