JavaRush /בלוג Java /Random-HE /אתה לא יכול לקלקל את ג'אווה עם שרשור: חלק א' - חוטים
Viacheslav
רָמָה

אתה לא יכול לקלקל את ג'אווה עם שרשור: חלק א' - חוטים

פורסם בקבוצה

מבוא

Multithreading מובנה ב-Java מאז ימיה הראשונים. אז בואו נסתכל במהירות על מה זה ריבוי שרשורים. אתה לא יכול להרוס את ג'אווה עם שרשור: חלק א' - חוטים - 1בואו ניקח את הלקח הרשמי מאורקל כנקודת התחלה: " שיעור: אפליקציית "הלו עולם! ". בואו נשנה מעט את הקוד של אפליקציית 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 אתה לא יכול להרוס את ג'אווה עם שרשור: חלק א' - חוטים - 2לקוד שלנו יש עכשיו פגם רציני. אם לא נעביר שום ארגומנט (כלומר פשוט נפעיל את Java HelloWorldApp), נקבל שגיאה:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
חריג (כלומר שגיאה) אירע בשרשור בשם main. מסתבר שיש איזה שרשורים בג'אווה? כאן מתחיל המסע שלנו.

ג'אווה וחוטים

כדי להבין מהו שרשור, עליך להבין כיצד מופעלת אפליקציית Java. בואו נשנה את הקוד שלנו באופן הבא:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			//Do nothing
		}
	}
}
עכשיו בואו נקמפל אותו שוב באמצעות javac. לאחר מכן, מטעמי נוחות, נריץ את קוד ה-Java שלנו בחלון נפרד. ב-Windows אתה יכול לעשות זאת כך: start java HelloWorldApp. כעת, באמצעות כלי השירות jps , בואו נראה איזה מידע ג'אווה יגיד לנו: אתה לא יכול להרוס את ג'אווה עם שרשור: חלק א' - חוטים - 3המספר הראשון הוא PID או Process ID, מזהה התהליך. מהו תהליך?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
בעזרת תהליכים, הביצוע של תוכניות שונות מבודדת זו מזו: כל אפליקציה משתמשת באזור הזיכרון שלה מבלי להפריע לתוכנות אחרות. אני ממליץ לך לקרוא את המאמר ביתר פירוט: " https://habr.com/post/164487/ ". תהליך לא יכול להתקיים ללא חוטים, כך שאם תהליך קיים, לפחות חוט אחד קיים בו. איך זה קורה בג'אווה? כאשר אנו מריצים תוכנית Java, הביצוע שלה מתחיל ב- main. אנחנו סוג של נכנסים לתוכנית, אז השיטה המיוחדת הזו mainנקראת נקודת הכניסה, או "נקודת הכניסה". השיטה mainחייבת להיות תמיד public static voidכך שה-Java Virtual Machine (JVM) יוכל להתחיל להפעיל את התוכנית שלנו. ראה " מדוע השיטה הראשית של Java היא סטטית? " לפרטים נוספים. מסתבר ש-java launcher (java.exe או javaw.exe) הוא יישום פשוט (יישום C פשוט): הוא טוען DLLs שונים, שהם למעשה ה-JVM. משגר Java מבצע קבוצה ספציפית של קריאות Java Native Interface (JNI). JNI הוא המנגנון שמגשר בין עולם ה-Java Virtual Machine ועולם ה-C++. מסתבר שהמשגר ​​הוא לא ה-JVM, אלא המעמיס שלו. הוא יודע את הפקודות הנכונות לביצוע כדי להפעיל את ה-JVM. יודע לארגן את כל הסביבה הדרושה באמצעות שיחות JNI. ארגון זה של הסביבה כולל גם יצירת חוט ראשי, הנקרא בדרך כלל main. כדי לראות בצורה ברורה יותר אילו שרשורים חיים בתהליך ג'אווה, אנו משתמשים בתוכנית jvisualvm , הכלולה ב-JDK. כשאנחנו יודעים את ה-pid של תהליך, אנחנו יכולים לפתוח נתונים עליו מיד: jvisualvm --openpid айдипроцесса אתה לא יכול להרוס את ג'אווה עם שרשור: חלק א' - חוטים - 4מעניין שלכל שרשור יש אזור נפרד משלו בזיכרון שהוקצה לתהליך. מבנה הזיכרון הזה נקרא מחסנית. ערימה מורכבת ממסגרות. מסגרת היא הנקודה של קריאה למתודה, נקודת ביצוע. מסגרת יכולה להיות מיוצגת גם כ-StackTraceElement (ראה Java API עבור StackTraceElement ). תוכל לקרוא עוד על הזיכרון שהוקצה לכל שרשור כאן . אם נסתכל על Java API ונחפש את המילה Thread, נראה שיש מחלקה java.lang.Thread . המחלקה הזו היא שמייצגת זרם בג'אווה, ועם זה אנחנו צריכים לעבוד. 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-demons. שרשורי Daemon הם שרשורי רקע (שרשורי שירות) שמבצעים עבודה מסוימת ברקע. המונח המעניין הזה הוא התייחסות ל"השד של מקסוול", עליו תוכלו לקרוא עוד במאמר בוויקיפדיה על " שדים ". כפי שצוין בתיעוד, ה-JVM ממשיך בביצוע התוכנית (תהליך) עד:
  • שיטת Runtime.exit אינה נקראת
  • כל השרשורים שאינם דמונים השלימו את עבודתם (הן ללא שגיאות והן עם חריגים שנזרקו)
מכאן הפרט החשוב: ניתן לסיים שרשורי דמון בכל פקודה שמתבצעת. לפיכך, שלמות הנתונים בהם אינה מובטחת. לכן, שרשורי דמון מתאימים למשימות שירות מסוימות. לדוגמה, ב-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);
}
חלוקה באפס תגרום לשגיאה שתיתפס על ידי המטפל. אם לא תציין את המטפל בעצמך, יישום המטפל המוגדר כברירת מחדל יעבוד, אשר יציג את ערימת השגיאות ב-StdError. ניתן לקרוא עוד בסקירה http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ ". בנוסף, לשרשור יש עדיפות. ניתן לקרוא עוד על סדרי עדיפויות ב- מאמר " עדיפות חוט ג'אווה בריבוי הליכי שרשור ".

יצירת שרשור

כפי שצוין בתיעוד, יש לנו 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. אסור לבלבל אותם, כי... אם נריץ את השיטה runישירות, לא יתחיל שרשור חדש. השיטה היא startשמבקשת מה-JVM ליצור שרשור חדש. האפשרות עם צאצא מ-Thread גרועה מכיוון שאנו כוללים את Thread בהיררכיית הכיתה. החיסרון השני הוא שאנחנו מתחילים להפר את עקרון ה"אחריות הבלעדית" מוצק, מכיוון הכיתה שלנו הופכת אחראית בו זמנית גם לניהול השרשור וגם למשימה כלשהי שיש לבצע בשרשור זה. מה נכון? התשובה היא בעצם השיטה 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