CodeGym /בלוג Java /Random-HE /עדיף ביחד: Java וכיתה Thread. חלק א' - חוטי הוצאה להורג
John Squirrels
רָמָה
San Francisco

עדיף ביחד: Java וכיתה Thread. חלק א' - חוטי הוצאה להורג

פורסם בקבוצה

מבוא

Multithreading היה מובנה ב-Java מההתחלה. אז, בואו נסתכל בקצרה על הדבר הזה שנקרא ריבוי פתילים. עדיף ביחד: Java וכיתה Thread.  חלק א' - חוטי הוצאה להורג - 1אנו לוקחים את הלקח הרשמי מאורקל כנקודת התייחסות: " שיעור: האפליקציה "שלום עולם! ". נשנה מעט את הקוד של תוכנית Hello World שלנו באופן הבא:
class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argsהוא מערך של פרמטרי קלט המועברים בעת הפעלת התוכנית. שמור את הקוד הזה לקובץ עם שם שתואם לשם המחלקה ויש לו את הסיומת .java. הידור אותו באמצעות תוכנית השירות javac : javac HelloWorldApp.java. לאחר מכן, אנו מריצים את הקוד שלנו עם פרמטר כלשהו, ​​למשל, "רוג'ר": java HelloWorldApp Roger עדיף ביחד: Java וכיתה Thread.  חלק א' - חוטי הוצאה להורג - 2לקוד שלנו יש כרגע פגם רציני. אם לא תעביר שום טיעון (כלומר הפעל רק "java HelloWorldApp"), נקבל שגיאה:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
חריג (כלומר שגיאה) אירע בשרשור בשם "ראשי". אז לג'אווה יש שרשורים? כאן מתחיל המסע שלנו.

Java וחוטים

כדי להבין מה זה שרשור, אתה צריך להבין איך תוכנית Java מתחילה. בואו נשנה את הקוד שלנו באופן הבא:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			// Do nothing
		}
	}
}
עכשיו בואו נקמפל אותו שוב עם javac. מטעמי נוחות, נריץ את קוד ה-Java שלנו בחלון נפרד. ב-Windows, ניתן לעשות זאת כך: start java HelloWorldApp. כעת נשתמש בכלי השירות jps כדי לראות איזה מידע Java יכולה לספר לנו: עדיף ביחד: Java וכיתה Thread.  חלק א' - חוטי הוצאה להורג - 3המספר הראשון הוא PID או Process ID. מהו תהליך?
A process is a combination of code and data sharing a common virtual address space.
עם תהליכים, תוכניות שונות מבודדות זו מזו בזמן שהן פועלות: כל יישום משתמש באזור משלו בזיכרון מבלי להפריע לתוכניות אחרות. כדי ללמוד עוד, אני ממליץ לקרוא את המדריך הזה: תהליכים וחוטים . תהליך לא יכול להתקיים בלי חוט, אז אם תהליך קיים, אז יש לו לפחות חוט אחד. אבל איך זה קורה בג'אווה? כאשר אנו מתחילים תוכנית Java, הביצוע מתחיל בשיטה main. זה כאילו אנחנו נכנסים לתוכנית, אז mainהשיטה המיוחדת הזו נקראת נקודת הכניסה. השיטה mainחייבת להיות תמיד "ריק סטטי ציבורי", כדי שהמכונה הוירטואלית של Java (JVM) תוכל להתחיל להפעיל את התוכנית שלנו. למידע נוסף, מדוע השיטה הראשית של Java היא סטטית? . מסתבר ש-Java Launcher (java.exe או javaw.exe) הוא יישום C פשוט: הוא טוען את ה-DLL השונים שמרכיבים למעשה את ה-JVM. משגר Java מבצע קבוצה ספציפית של קריאות Java Native Interface (JNI). JNI הוא מנגנון לחיבור עולם המכונה הוירטואלית של Java עם עולם ה-C++. אז, המשגר ​​אינו ה-JVM עצמו, אלא מנגנון לטעינת אותו. הוא יודע את הפקודות הנכונות לביצוע כדי להפעיל את ה-JVM. הוא יודע להשתמש בשיחות JNI כדי להגדיר את הסביבה הדרושה. הגדרת סביבה זו כוללת יצירת השרשור הראשי, הנקרא כמובן "ראשי". כדי להמחיש טוב יותר אילו שרשורים קיימים בתהליך Java, אנו משתמשים בכלי jvisualvm , הכלול ב-JDK. כשאנחנו יודעים את ה-pid של תהליך, אנחנו יכולים לראות מיד מידע על התהליך הזה: jvisualvm --openpid <process id> עדיף ביחד: Java וכיתה Thread.  חלק א' - חוטי הוצאה להורג - 4מעניין שלכל חוט יש אזור נפרד משלו בזיכרון שהוקצה לתהליך. מבנה הזיכרון הזה נקרא מחסנית. ערימה מורכבת ממסגרות. מסגרת מייצגת את ההפעלה של שיטה (קריאת שיטה לא גמורה). מסגרת יכולה להיות מיוצגת גם כ-StackTraceElement (ראה את Java API עבור StackTraceElement ). תוכל למצוא מידע נוסף על הזיכרון שהוקצה לכל שרשור בדיון כאן: " איך Java (JVM) מקצה מחסנית לכל שרשור ". אם תסתכל על Java API ותחפש את המילה "Thread", תמצא את המחלקה java.lang.Thread . זו המחלקה שמייצגת שרשור ב-Java, ונצטרך לעבוד איתה. עדיף ביחד: Java וכיתה Thread.  חלק א' - חוטי הוצאה להורג - 5

Java.lang.Thread

ב-Java, שרשור מיוצג על ידי מופע של המחלקה java.lang.Thread. עליך להבין מיד שמופעים של המחלקה Thread אינם בעצמם שרשורי ביצוע. זהו רק סוג של API עבור השרשורים ברמה נמוכה המנוהלים על ידי ה-JVM ומערכת ההפעלה. כאשר אנו מתחילים את ה-JVM באמצעות משגר ג'אווה, הוא יוצר שרשור mainשנקרא "ראשי" ועוד כמה שרשורי משק בית. כפי שצוין ב-JavaDoc למחלקה Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread. ישנם 2 סוגי שרשורים: דמונים ולא-דמונים. שרשורי 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);
}
חלוקה באפס תגרום לשגיאה שתיתפס על ידי המטפל. אם לא תציין את המטפל שלך, אז ה-JVM יפעיל את המטפל המוגדר כברירת מחדל, אשר יוציא את עקבות המחסנית של החריג ל-StdError. לכל שרשור יש גם עדיפות. אתה יכול לקרוא עוד על סדרי עדיפויות במאמר זה: עדיפות חוט ג'אווה בריבוי הליכי שרשור .

יצירת שרשור

כפי שצוין בתיעוד, יש לנו 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, שאנו יכולים להעביר בעת יצירת מופע של המחלקה 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();
}

סיכום

אני מקווה שהדיון הזה מבהיר מה זה שרשור, איך נוצרים שרשורים, ואילו פעולות בסיסיות אפשר לבצע עם שרשורים. בחלק הבא , ננסה להבין כיצד שרשורים מתקשרים זה עם זה ולחקור את מחזור החיים של השרשור. עדיף ביחד: Java וכיתה Thread. חלק ב' - סנכרון טוב יותר ביחד: Java ומחלקת Thread. חלק שלישי - אינטראקציה טובה יותר ביחד: Java ומחלקת Thread. חלק IV — ניתן להתקשרות, עתיד וחברים טובים יותר ביחד: Java וכיתה Thread. חלק V - Executor, ThreadPool, Fork/Join Better יחד: Java ומחלקת Thread. חלק ו' - אש!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION