JavaRush /จาวาบล็อก /Random-TH /คุณไม่สามารถสปอย Java ด้วยเธรด: ตอนที่ 1 - เธรด
Viacheslav
ระดับ

คุณไม่สามารถสปอย Java ด้วยเธรด: ตอนที่ 1 - เธรด

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

การแนะนำ

Multithreading ถูกสร้างขึ้นใน Java มาตั้งแต่วันแรก ลองมาดูกันคร่าวๆ ว่า multithreading คืออะไร คุณไม่สามารถทำลาย Java ด้วย Thread: Part I - Threads - 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 ด้วย Thread: Part I - Threads - 2ตอนนี้โค้ดของเรามีข้อบกพร่องร้ายแรง หากเราไม่ผ่านการโต้แย้งใดๆ (เช่น เพียงเรียกใช้งาน Java HelloWorldApp) เราจะได้รับข้อผิดพลาด:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
มีข้อยกเว้น (เช่น ข้อผิดพลาด) เกิดขึ้นในเธรดชื่อmain. ปรากฎว่ามีเธรดบางประเภทใน Java? นี่คือจุดเริ่มต้นของการเดินทางของเรา

Java และเธรด

เพื่อทำความเข้าใจว่าเธรดคืออะไร คุณต้องเข้าใจว่าแอปพลิเคชัน Java ถูกเรียกใช้งานอย่างไร มาเปลี่ยนรหัสของเราดังนี้:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			//Do nothing
		}
	}
}
ตอนนี้เรามาคอมไพล์อีกครั้งโดยใช้ javac ต่อไป เพื่อความสะดวก เราจะเรียกใช้โค้ด Java ของเราในหน้าต่างแยกต่างหาก บน Windows คุณสามารถทำได้ดังนี้: start java HelloWorldApp. ตอนนี้ เมื่อใช้ ยูทิลิตี้ jpsเรามาดูกันว่าข้อมูลใดที่ Java จะบอกเรา: คุณไม่สามารถทำลาย Java ด้วย Thread: Part I - Threads - 3ตัวเลขแรกคือ PID หรือ Process ID ซึ่งเป็นตัวระบุกระบวนการ กระบวนการคืออะไร?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
ด้วยความช่วยเหลือของกระบวนการ การทำงานของโปรแกรมต่างๆ จะถูกแยกออกจากกัน: แต่ละแอปพลิเคชันใช้พื้นที่หน่วยความจำของตัวเองโดยไม่รบกวนโปรแกรมอื่น ฉันแนะนำให้คุณอ่านบทความโดยละเอียด: " https://habr.com/post/164487/ " กระบวนการไม่สามารถดำรงอยู่ได้หากไม่มีเธรด ดังนั้นหากมีกระบวนการอยู่ ก็จะมีอย่างน้อยหนึ่งเธรดอยู่ในนั้น สิ่งนี้เกิดขึ้นได้อย่างไรใน Java? เมื่อเรารันโปรแกรม Java การดำเนินการจะเริ่มต้นด้วยนามสกุลmain. เราค่อนข้างจะเข้าสู่โปรแกรม ดังนั้นวิธีการพิเศษนี้mainจึงเรียกว่าจุดเข้าหรือ "จุดเข้า" วิธีการmainจะต้องเป็นเช่นนั้นเสมอpublic static voidเพื่อให้ Java Virtual Machine (JVM) สามารถเริ่มรันโปรแกรมของเราได้ ดู " เหตุใดวิธีการหลักของ Java จึงคงที่ " สำหรับรายละเอียดเพิ่มเติม ปรากฎว่าตัวเรียกใช้งาน Java (java.exe หรือ javaw.exe) เป็นแอปพลิเคชันธรรมดา (แอปพลิเคชัน C แบบง่าย): โหลด DLL ต่างๆ ซึ่งจริงๆ แล้วคือ JVM ตัวเรียกใช้งาน Java สร้างชุดการเรียก Java Native Interface (JNI) เฉพาะ JNI เป็นกลไกที่เชื่อมโยงโลกของ Java Virtual Machine และโลกของ C++ ปรากฎว่าตัวเรียกใช้งานไม่ใช่ JVM แต่เป็นตัวโหลด รู้คำสั่งที่ถูกต้องในการดำเนินการเพื่อเริ่มต้น JVM รู้วิธีจัดระเบียบสภาพแวดล้อมที่จำเป็นทั้งหมดโดยใช้การเรียก JNI การจัดระเบียบสภาพแวดล้อมนี้ยังรวมถึงการสร้างเธรดหลัก ซึ่งโดยปกติจะเรียกmainว่า เพื่อให้เห็นได้ชัดเจนยิ่งขึ้นว่าเธรดใดอยู่ในกระบวนการจาวา เราใช้ โปรแกรม jvisualvmซึ่งรวมอยู่ใน JDK เมื่อรู้ pid ของกระบวนการ เราก็สามารถเปิดข้อมูลได้ทันทีjvisualvm --openpid айдипроцесса คุณไม่สามารถทำลาย Java ด้วย Thread: Part I - Threads - 4ที่น่าสนใจคือ แต่ละเธรดมีพื้นที่แยกต่างหากในหน่วยความจำที่จัดสรรไว้สำหรับกระบวนการ โครงสร้างหน่วยความจำนี้เรียกว่าสแต็ก สแต็กประกอบด้วยเฟรม เฟรมคือจุดของการเรียกเมธอด จุดดำเนินการ เฟรมยังสามารถแสดงเป็น StackTraceElement ได้ (ดู Java API สำหรับStackTraceElement ) คุณสามารถอ่านเพิ่มเติมเกี่ยวกับหน่วยความจำที่จัดสรรให้กับแต่ละเธรดได้ที่นี่ หากเราดูที่Java APIและค้นหาคำว่า Thread เราจะเห็นว่ามีคลาสjava.lang.Thread คลาสนี้เองที่แสดงถึงสตรีมใน Java และด้วยเหตุนี้เองที่เราต้องทำงาน Thread'ом Java не испортишь: Часть I — потоки - 5

java.lang.Thread

เธรดใน Java ถูกแสดงเป็นอินสแตนซ์ของjava.lang.Threadคลาส ควรทำความเข้าใจทันทีว่าอินสแตนซ์ของคลาส Thread ใน Java ไม่ใช่เธรดในตัวมันเอง นี่เป็นเพียง API ชนิดหนึ่งสำหรับเธรดระดับต่ำที่จัดการโดย JVM และระบบปฏิบัติการ เมื่อเราเปิดตัว JVM โดยใช้ตัวเรียกใช้งาน Java มันจะสร้างเธรดหลักพร้อมชื่อmainและเธรดบริการอีกมากมาย ตามที่ระบุไว้ใน JavaDoc ของคลาส Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread มีเธรด 2 ประเภท: daemons และ non-daemons เธรด Daemon คือเธรดพื้นหลัง (เธรดบริการ) ที่ทำงานบางอย่างในเบื้องหลัง คำที่น่าสนใจนี้เป็นการอ้างอิงถึง "ปีศาจของ Maxwell" ซึ่งคุณสามารถอ่านเพิ่มเติมได้ในบทความ Wikipedia เกี่ยวกับ " ปีศาจ " ตามที่ระบุไว้ในเอกสารประกอบ JVM ดำเนินการโปรแกรม (กระบวนการ) ต่อไปจนกระทั่ง:
  • ไม่ได้เรียกเมธอดRuntime.exit
  • เธรดที่ไม่ใช่ daemon ทั้งหมดทำงานเสร็จสิ้นแล้ว (ทั้งที่ไม่มีข้อผิดพลาดและมีข้อยกเว้นเกิดขึ้น)
ดังนั้นรายละเอียดที่สำคัญ: เธรด daemon สามารถยุติได้ในคำสั่งใด ๆ ที่กำลังดำเนินการ ดังนั้นจึงไม่รับประกันความสมบูรณ์ของข้อมูลในนั้น ดังนั้นเธรด daemon จึงเหมาะสำหรับงานบริการบางอย่าง ตัวอย่างเช่น ใน Java มีเธรดที่รับผิดชอบในการประมวลผลวิธีการสรุปหรือเธรดที่เกี่ยวข้องกับ 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);
}
การหารด้วยศูนย์จะทำให้เกิดข้อผิดพลาดที่ตัวจัดการจะตรวจจับได้ หากคุณไม่ได้ระบุตัวจัดการด้วยตนเอง การใช้งานตัวจัดการเริ่มต้นจะทำงานได้ ซึ่งจะแสดงสแต็กข้อผิดพลาดใน StdError คุณสามารถอ่านเพิ่มเติมได้ในบทวิจารณ์http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ " นอกจากนี้ เธรดยังมีลำดับความสำคัญ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับลำดับความสำคัญได้ใน บทความ " Java Thread Priority ใน 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();
    }
}
อย่างที่คุณเห็น งานจะถูกเปิดใช้งานใน method runและ thread จะถูกเปิดใช้งานในstartmethod ไม่ควรสับสนเพราะ... ถ้าเรารันเมธอดrunโดยตรง จะไม่มีการเริ่มเธรดใหม่ เป็นวิธีการstartที่ขอให้ JVM สร้างเธรดใหม่ ตัวเลือกที่มีการสืบทอดจาก Thread ไม่ดีเนื่องจากเรารวม Thread ไว้ในลำดับชั้นของชั้นเรียน ข้อเสียอย่างที่สองคือเรากำลังเริ่มละเมิดหลักการ “Sole Responsibility” SOLID เพราะ ชั้นเรียนของเราจะรับผิดชอบทั้งการจัดการเธรดและงานบางอย่างที่ต้องดำเนินการในเธรดนี้ไปพร้อม ๆ กัน ข้อไหนถูกต้อง? คำตอบอยู่ในวิธีการเดียวrunกับที่เราแทนที่:
public void run() {
	if (target != null) {
		target.run();
	}
}
นี่targetคือบางส่วนjava.lang.Runnableซึ่งเราสามารถส่งผ่านไปยัง Thread เมื่อสร้างอินสแตนซ์ของคลาส ดังนั้นเราจึงสามารถทำได้:
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();
}

ทั้งหมด

ดังนั้น ฉันหวังว่าจากเรื่องราวนี้จะมีความชัดเจนว่ากระแสคืออะไร มีอยู่อย่างไร และการดำเนินการพื้นฐานใดบ้างที่สามารถทำได้กับกระแสเหล่านั้น ในส่วนถัดไปควรทำความเข้าใจว่าเธรดมีปฏิสัมพันธ์กันอย่างไร และวงจรชีวิตของเธรดคืออะไร #เวียเชสลาฟ
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION