CodeGym /จาวาบล็อก /สุ่ม /ดีกว่ากัน: Java และคลาสเธรด ส่วนที่ 1 - เธรดของการดำเนินก...
John Squirrels
ระดับ
San Francisco

ดีกว่ากัน: Java และคลาสเธรด ส่วนที่ 1 - เธรดของการดำเนินการ

เผยแพร่ในกลุ่ม

การแนะนำ

มัลติเธรดถูกสร้างขึ้นใน Java ตั้งแต่เริ่มต้น มาดูสิ่งที่เรียกว่ามัลติเธรดโดยสังเขป ดีกว่ากัน: Java และคลาสเธรด  ส่วนที่ 1 — เธรดการดำเนินการ - 1เราใช้บทเรียนอย่างเป็นทางการจาก Oracle เป็นจุดอ้างอิง: " บทเรียน: แอปพลิเคชัน "Hello World! " เราจะแก้ไขโค้ดของโปรแกรม Hello World เล็กน้อยดังนี้:

class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argsเป็นอาร์เรย์ของพารามิเตอร์อินพุตที่ส่งผ่านเมื่อโปรแกรมเริ่มทำงาน บันทึกรหัสนี้ลงในไฟล์ที่มีชื่อตรงกับชื่อคลาสและมีนามสกุล.java. คอมไพล์โดยใช้ ยูทิลิตี javac : javac HelloWorldApp.java. จากนั้น เราเรียกใช้โค้ดของเราด้วยพารามิเตอร์บางตัว เช่น "Roger": java HelloWorldApp Roger ดีกว่ากัน: Java และคลาสเธรด  ส่วนที่ 1 — เธรดการดำเนินการ - 2ขณะนี้โค้ดของเรามีข้อบกพร่องร้ายแรง หากคุณไม่ผ่านอาร์กิวเมนต์ใดๆ (เช่น ดำเนินการเพียงแค่ "java HelloWorldApp") เราจะได้รับข้อผิดพลาด:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
เกิดข้อยกเว้น (เช่น ข้อผิดพลาด) ในเธรดชื่อ "หลัก" ดังนั้น Java มีเธรดหรือไม่ นี่คือจุดเริ่มต้นของการเดินทางของเรา

Java และเธรด

เพื่อให้เข้าใจว่าเธรดคืออะไร คุณต้องเข้าใจว่าโปรแกรม Java เริ่มทำงานอย่างไร มาเปลี่ยนรหัสของเราดังนี้:

class HelloWorldApp {
    public static void main(String[] args) {
		while (true) { 
			// Do nothing
		}
	}
}
ทีนี้มาคอมไพล์อีกครั้งด้วยjavac. เพื่อความสะดวก เราจะเรียกใช้โค้ด Java ในหน้าต่างแยกต่างหาก บน Windows สามารถทำได้ดังนี้: start java HelloWorldApp. ตอนนี้เราจะใช้ ยูทิลิตี้ jpsเพื่อดูว่า Java สามารถบอกข้อมูลใดได้บ้าง: ดีกว่ากัน: Java และคลาสเธรด  ส่วนที่ 1 — เธรดการดำเนินการ - 3หมายเลขแรกคือ PID หรือ Process ID กระบวนการคืออะไร?

A process is a combination of code and data sharing a common virtual address space.
ด้วยกระบวนการ โปรแกรมต่างๆ จะถูกแยกออกจากกันในขณะที่รัน: แต่ละแอปพลิเคชันใช้พื้นที่ของตัวเองในหน่วยความจำโดยไม่รบกวนการทำงานของโปรแกรมอื่นๆ หากต้องการเรียนรู้เพิ่มเติม ฉันแนะนำให้อ่านบทช่วยสอนนี้: กระบวนการและเธรด กระบวนการไม่สามารถดำรงอยู่ได้หากไม่มีเธรด ดังนั้นหากกระบวนการมีอยู่ กระบวนการนั้นจะต้องมีเธรดอย่างน้อยหนึ่งเธรด แต่สิ่งนี้เกิดขึ้นได้อย่างไรใน Java เมื่อเราเริ่มโปรแกรม Java การดำเนินการจะเริ่มต้นด้วยmainเมธอด เหมือนกับว่าเรากำลังก้าวเข้าสู่โปรแกรม ดังนั้นmainวิธีการพิเศษนี้จึงเรียกว่าจุดเข้าใช้งาน เมธอด นี้mainต้องเป็น "public static void" เสมอ เพื่อให้ Java virtual machine (JVM) เริ่มรันโปรแกรมของเราได้ สำหรับข้อมูลเพิ่มเติมเหตุใดเมธอดหลักของ Java จึงเป็นสแตติก. ปรากฎว่า Java launcher (java.exe หรือ javaw.exe) เป็นแอปพลิเคชัน C แบบง่าย: มันโหลด DLL ต่างๆ ที่ประกอบเป็น JVM Java Launcher สร้างชุดการเรียก Java Native Interface (JNI) เฉพาะ JNI เป็นกลไกสำหรับเชื่อมต่อโลกของเครื่องเสมือน Java กับโลกของ C++ ดังนั้นตัวเรียกใช้งานจึงไม่ใช่ตัว JVM แต่เป็นกลไกในการโหลด มันรู้คำสั่งที่ถูกต้องในการดำเนินการเพื่อเริ่มต้น JVM รู้วิธีใช้การเรียก JNI เพื่อตั้งค่าสภาพแวดล้อมที่จำเป็น การตั้งค่าสภาพแวดล้อมนี้รวมถึงการสร้างเธรดหลักซึ่งเรียกว่า "หลัก" แน่นอน เพื่อแสดงให้เห็นได้ดีขึ้นว่าเธรดใดอยู่ในกระบวนการ Java เราใช้jvisualvmเครื่องมือซึ่งรวมอยู่ใน JDK เมื่อทราบ pid ของกระบวนการ เราสามารถดูข้อมูลเกี่ยวกับกระบวนการนั้นได้ทันที: jvisualvm --openpid <process id> ดีกว่ากัน: Java และคลาสเธรด  ส่วนที่ 1 — เธรดการดำเนินการ - 4ที่น่าสนใจคือ แต่ละเธรดมีพื้นที่แยกต่างหากในหน่วยความจำที่จัดสรรให้กับกระบวนการ โครงสร้างหน่วยความจำนี้เรียกว่าสแตก สแต็คประกอบด้วยเฟรม เฟรมแสดงถึงการเปิดใช้งานเมธอด (การเรียกเมธอดที่ยังไม่เสร็จ) เฟรมยังสามารถแสดงเป็น StackTraceElement (ดู Java API สำหรับStackTraceElement ) คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับหน่วยความจำที่จัดสรรให้กับแต่ละเธรดได้ในการสนทนาที่นี่: " Java (JVM) จัดสรรสแต็กสำหรับแต่ละเธรดอย่างไร " หากคุณดูที่Java APIและค้นหาคำว่า "Thread" คุณจะพบjava.lang.Threadระดับ. นี่คือคลาสที่แสดงถึงเธรดใน Java และเราจะต้องทำงานกับมัน ดีกว่ากัน: Java และคลาสเธรด  ส่วนที่ 1 — เธรดการดำเนินการ - 5

java.lang.Thread

ใน Java เธรดจะแสดงโดยอินสแตนซ์ของjava.lang.Threadคลาส คุณควรเข้าใจทันทีว่าอินสแตนซ์ของคลาสเธรดไม่ใช่เธรดของการดำเนินการ นี่เป็นเพียง API ประเภทหนึ่งสำหรับเธรดระดับต่ำที่จัดการโดย JVM และระบบปฏิบัติการ เมื่อเราเริ่มต้น JVM โดยใช้ Java Launcher จะสร้างmainเธรดชื่อ "หลัก" และเธรดการดูแลทำความสะอาดอื่นๆ อีกสองสามเธรด ตามที่ระบุไว้ใน JavaDoc สำหรับคลาส Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread. เธรดมี 2 ประเภท: daemon และ non-daemons เธรด Daemon เป็นเธรดพื้นหลัง (การดูแลทำความสะอาด) ที่ทำงานบางอย่างในพื้นหลัง คำว่า "daemon" หมายถึงปีศาจของ Maxwell คุณสามารถเรียนรู้เพิ่มเติม ได้ในบทความ Wikipedia นี้ ตามที่ระบุไว้ในเอกสารประกอบ JVM ดำเนินการโปรแกรม (กระบวนการ) ต่อไปจนกระทั่ง:
  • มีการเรียกใช้เมธอดRuntime.exit ()
  • เธรด NON-daemon ทั้งหมดเสร็จสิ้นการทำงาน (โดยไม่มีข้อผิดพลาดหรือมีข้อยกเว้น)
รายละเอียดที่สำคัญต่อจากนี้: เธรด daemon สามารถยุติได้ทุกเมื่อ ด้วยเหตุนี้จึงไม่มีการรับประกันเกี่ยวกับความสมบูรณ์ของข้อมูล ดังนั้น เธรดภูตจึงเหมาะสำหรับงานทำความสะอาดบางอย่าง ตัวอย่างเช่น Java มีเธรดที่รับผิดชอบสำหรับfinalize()การเรียกเมธอดการประมวลผล เช่น เธรดที่เกี่ยวข้องกับ Garbage Collector (gc) แต่ละเธรดเป็นส่วนหนึ่งของกลุ่ม ( ThreadGroup ) และกลุ่มต่างๆ สามารถเป็นส่วนหนึ่งของกลุ่มอื่นๆ โดยสร้างลำดับชั้นหรือโครงสร้างที่แน่นอน

public static void main(String[] args) {
	Thread currentThread = Thread.currentThread();
	ThreadGroup threadGroup = currentThread.getThreadGroup();
	System.out.println("Thread: " + currentThread.getName());
	System.out.println("Thread Group: " + threadGroup.getName());
	System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
กลุ่มนำคำสั่งไปยังการจัดการเธรด นอกเหนือจากกลุ่มแล้ว เธรดยังมีตัวจัดการข้อยกเว้นของตัวเอง ลองดูตัวอย่าง:

public static void main(String[] args) {
	Thread th = Thread.currentThread();
	th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
		@Override
		public void uncaughtException(Thread t, Throwable e) {
			System.out.println("An error occurred: " + e.getMessage());
		}
	});
    System.out.println(2/0);
}
การหารด้วยศูนย์จะทำให้เกิดข้อผิดพลาดที่ตัวจัดการจะจับได้ หากคุณไม่ระบุตัวจัดการของคุณเอง JVM จะเรียกใช้ตัวจัดการเริ่มต้น ซึ่งจะส่งออกการติดตามสแต็กของข้อยกเว้นไปยัง StdError แต่ละเธรดยังมีลำดับความสำคัญ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับลำดับความสำคัญได้ในบทความนี้: Java Thread Priority in Multithreading

การสร้างเธรด

ตามที่ระบุไว้ในเอกสาร เรามี 2 วิธีในการสร้างเธรด วิธีแรกคือสร้างคลาสย่อยของคุณเอง ตัวอย่างเช่น:

public class HelloWorld{
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello, World!");  
        }
    }
    
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}
อย่างที่คุณเห็น การทำงานของงานเกิดขึ้นในเมธอดrun()แต่เธรดนั้นเริ่มต้นในstart()เมธอด อย่าสับสนกับเมธอดเหล่านี้: หากเราเรียกun()เมธอด r โดยตรง ก็จะไม่มีการเริ่มเธรดใหม่ เป็นstart()วิธีการที่ขอให้ JVM สร้างเธรดใหม่ ตัวเลือกที่เราสืบทอด Thread นี้ไม่ดีอยู่แล้วเนื่องจากเรารวม Thread ไว้ในลำดับชั้นของคลาส ข้อเสียประการที่สองคือเรากำลังเริ่มละเมิดหลักการ "ความรับผิดชอบเดียว" นั่นคือชั้นเรียนของเรามีหน้าที่รับผิดชอบในการควบคุมเธรดและงานบางอย่างที่จะดำเนินการในเธรดนี้ วิธีที่ถูกต้องคืออะไร? พบคำตอบในrun()วิธีการเดียวกันซึ่งเราแทนที่:

public void run() {
	if (target != null) {
		target.run();
	}
}
นี่targetคือบางส่วนjava.lang.Runnableซึ่งเราสามารถผ่านได้เมื่อสร้างอินสแตนซ์ของคลาสเธรด หมายความว่าเราสามารถทำได้:

public class HelloWorld{
    public static void main(String[] args) {
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("Hello, World!");
            } 
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Runnableยังเป็นอินเทอร์เฟซการทำงานตั้งแต่ Java 1.8 สิ่งนี้ทำให้สามารถเขียนโค้ดที่สวยงามยิ่งขึ้นสำหรับงานของเธรด:

public static void main(String[] args) {
	Runnable task = () -> { 
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

บทสรุป

ฉันหวังว่าการสนทนานี้จะอธิบายให้ชัดเจนว่าเธรดคืออะไร เธรดเกิดขึ้นได้อย่างไร และการดำเนินการพื้นฐานใดที่สามารถดำเนินการกับเธรดได้ ในส่วนถัดไปเราจะพยายามทำความเข้าใจว่าเธรดโต้ตอบกันอย่างไร และสำรวจวงจรชีวิตของเธรด ดีกว่ากัน: Java และคลาสเธรด ส่วนที่ II — การซิงโครไนซ์ ร่วมกันได้ดีขึ้น: Java และคลาสเธรด ส่วนที่ 3 — ปฏิสัมพันธ์ ร่วมกันได้ดีขึ้น: Java และคลาสเธรด ตอนที่ IV — Callable, Future และผองเพื่อน อยู่ด้วยกันดีกว่า: Java และคลาส Thread ส่วนที่ V — Executor, ThreadPool, Fork/Jin Better together: Java และคลาส Thread ตอนที่ VI — ยิงออกไป!
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION