היי! היום נמשיך לדבר על ריבוי שרשורים. הבה נבחן את המחלקה Thread ומה עושות כמה מהשיטות שלה. כאשר למדנו שיטות כיתה בעבר, בדרך כלל כתבנו את זה: <שם שיטה> -> <מה השיטה עושה>. זה לא יעבוד עם
Thread
שיטות של :) יש להן היגיון מורכב יותר שלא תוכל להבין בלי כמה דוגמאות.
שיטת Thread.start()
נתחיל בלחזור על עצמנו. כפי שאתה בוודאי זוכר, אתה יכול ליצור שרשור על ידי כך שהכיתה שלך תירש אתThread
המחלקה ועקוף את run()
השיטה. אבל זה לא יתחיל מעצמו, כמובן. לשם כך, אנו קוראים לשיטת האובייקט שלנו start()
. נזכיר את הדוגמה מהשיעור הקודם:
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
הערה: כדי לפתוח שרשור, עליך לקרוא לשיטה המיוחדתstart()
ולא לשיטהrun()
! זוהי שגיאה קלה לביצוע, במיוחד כאשר אתה מתחיל ללמוד ריבוי הליכות. בדוגמה שלנו, אם תקרא לשיטהrun()
10 פעמים במקוםstart()
, תקבל את זה:
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.run();
}
}
}
תסתכל על התוצאות של התוכנית שלנו: שרשור הוצא לפועל: שרשור-0 שרשור הופעל: שרשור-1 שרשור הופעל: שרשור-2 שרשור הוצא לפועל: שרשור-3 שרשור הוצא לפועל: שרשור-4 השרשור הופעל: אשכול-5 השרשור הופעל: אשכול-6 שרשור בוצע: שרשור-7 שרשור הוצא לפועל: שרשור-8 שרשור הוצא לפועל: שרשור-9 תסתכל על סדר הפלט: הכל מתרחש בסדר מושלם. מוזר, הא? אנחנו לא רגילים לזה, כי אנחנו כבר יודעים שהסדר שבו שרשורים מתחילים ומבוצעים נקבע על ידי אינטלקט מעולה בתוך מערכת ההפעלה שלנו: מתזמן השרשורים. אולי פשוט התמזל מזלנו? כמובן, זה לא עניין של מזל. אתה יכול לאמת זאת על ידי הפעלת התוכנית עוד כמה פעמים. הבעיה היא שלקריאה לשיטה run()
ישירות אין שום קשר לריבוי ההליכים. במקרה זה, התוכנית תבוצע על השרשור הראשי, אותו שרשור שמבצע את main()
השיטה. זה פשוט מדפיס ברצף 10 שורות על הקונסולה וזהו. 10 שרשורים לא נפתחו. אז, זכרו את זה בעתיד ובדקו את עצמכם כל הזמן. אם אתה רוצה run()
לקרוא לשיטה, התקשר ל- start()
. בוא נלך רחוק יותר.
שיטת Thread.sleep()
כדי להשהות את ביצוע השרשור הנוכחי לזמן מה, אנו משתמשים בשיטהsleep()
. השיטה sleep()
לוקחת מספר אלפיות שניות כארגומנט, המציין את משך הזמן להרדם את החוט.
public class Main {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(3000);
System.out.println(" - How long did I sleep? \n - " + ((System.currentTimeMillis()-start)) / 1000 + " seconds");
}
}
פלט קונסולה: - כמה זמן ישנתי? - 3 שניות הערה: השיטה sleep()
היא סטטית: היא משינה את השרשור הנוכחי. כלומר, זה שהוצא להורג כרגע. הנה עוד נקודה חשובה: חוט שינה יכול להיקטע. במקרה זה, התוכנית זורקת InterruptedException
. נשקול דוגמה להלן. אגב, מה קורה אחרי שהשרשור מתעורר? האם זה ימשיך להתבצע ממש מהמקום שבו הפסיקה? לא. לאחר שרשור מתעורר, כלומר הזמן שעבר כטיעון אל Thread.sleep()
עבר, הוא עובר למצב שניתן להפעיל . אבל, זה לא אומר שמתזמן השרשור יריץ אותו. יתכן שהוא ייתן עדיפות לחוט אחר שאינו ישן ולאפשר לחוט שהתעורר הטרי שלנו להמשיך בעבודתו מעט מאוחר יותר. הקפד לזכור את זה: להתעורר לא אומר להמשיך לעבוד מיד!
שיטת Thread.join()
השיטהjoin()
משעה את ביצוע השרשור הנוכחי עד לסיום שרשור אחר. אם יש לנו 2 שרשורים, t1
ו t2
, ואנחנו כותבים
t1.join()
אז t2
לא יתחיל עד t1
שיסיים את עבודתו. השיטה join()
יכולה לשמש כדי להבטיח את סדר הביצוע של חוטים. הבה נבחן כיצד join()
השיטה פועלת בדוגמה הבאה:
public class ThreadExample extends Thread {
@Override
public void run() {
System.out.println("Thread started: " + getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + getName() + " is finished.");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadExample t1 = new ThreadExample();
ThreadExample t2 = new ThreadExample();
t1.start();
/* The second thread (t2) will start running only after the first thread (t1)
is finished (or an exception is thrown) */
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
// The main thread will continue running only after t1 and t2 have finished
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All threads have finished. The program is finished.");
}
}
יצרנו ThreadExample
שיעור פשוט. המשימה שלו היא להציג הודעה שהשרשור התחיל, להירדם ל-5 שניות, ואז לבסוף לדווח שהעבודה הושלמה. קלי קלות. ההיגיון העיקרי הוא בכיתה Main
. תסתכל על ההערות: אנו משתמשים בשיטה join()
כדי לנהל בהצלחה את סדר הביצוע של השרשורים. אם אתה זוכר איך התחלנו את הנושא הזה, סדר הביצוע מטופל על ידי מתזמן השרשור. הוא מפעיל שרשורים לפי שיקול דעתו: בכל פעם בצורה אחרת. כאן אנו משתמשים בשיטה כדי להבטיח שהשרשור t1
יתחיל ויבוצע קודם כל, אחר כך t2
השרשור, ורק לאחריו ימשיך השרשור הראשי של התוכנית. ממשיך הלאה. בתוכניות אמיתיות, לעתים קרובות תמצא מצבים שבהם תצטרך להפריע לביצוע שרשור. לדוגמה, השרשור שלנו פועל, אבל הוא ממתין לאירוע או מצב מסוים. אם זה קורה, החוט נעצר. זה כנראה יהיה הגיוני אם הייתה איזושהי stop()
שיטה. אבל זה לא כל כך פשוט. פעם, ל-Java אכן הייתה Thread.stop()
שיטה ואפשרה להפריע לשרשור. אבל זה הוסר מאוחר יותר מספריית Java. אתה יכול למצוא אותו בתיעוד של Oracle ולראות שהוא מסומן כמוצא משימוש . למה? כי זה פשוט עצר את השרשור בלי לעשות שום דבר אחר. לדוגמה, ייתכן שהשרשור עובד עם נתונים ומשנה משהו. ואז באמצע עבודתו היא נקטעה בפתאומיות וללא טקס על ידי stop()
השיטה. בלי כיבוי מתאים, גם לא שחרור משאבים, אפילו לא טיפול בשגיאות - לא היה כל זה. אם להגזים מעט, stop()
השיטה פשוט הרסה את כל מה שבדרכה. זה היה כמו למשוך את כבל החשמל מהשקע כדי לכבות את המחשב. כן, אתה יכול לקבל את התוצאה הרצויה. אבל כולם יודעים שאחרי כמה שבועות המחשב לא יודה לך על כך שאתה מתייחס אליו. זו הסיבה שהלוגיקה של הפסקת שרשורים השתנתה ב-Java ועכשיו היא משתמשת בשיטה מיוחדת interrupt()
.
שיטת Thread.interrupt()
מה קורה אםinterrupt()
השיטה נקראת על שרשור? יש 2 אפשרויות:
- אם האובייקט היה במצב המתנה, למשל, עקב שיטות
join
אוsleep
, ההמתנה תופסק והתוכנית תזרוקInterruptedException
. - אם השרשור היה במצב תקין,
interrupted
הדגל הבוליאני יוגדר על האובייקט.
Thread
לכיתה יש את boolean isInterrupted()
השיטה. נחזור לדוגמה השעון שהייתה בשיעור בקורס הבסיסי. מטעמי נוחות, פישטנו אותו מעט:
public class Clock extends Thread {
public static void main(String[] args) throws InterruptedException {
Clock clock = new Clock();
clock.start();
Thread.sleep(10000);
clock.interrupt();
}
public void run() {
Thread current = Thread.currentThread();
while (!current.isInterrupted())
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("The thread was interrupted");
break;
}
System.out.println("Tick");
}
}
}
במקרה זה, השעון מופעל ומתחיל לתקתק כל שנייה. בשניה העשירית אנו קוטעים את חוט השעון. כפי שאתה כבר יודע, אם השרשור שאנו מנסים להפריע נמצא באחד ממצבי ההמתנה, התוצאה היא InterruptedException
. זהו חריג מסומן, כך שנוכל לתפוס אותו בקלות ולבצע את ההיגיון שלנו כדי לסיים את התוכנית. וזה בדיוק מה שעשינו. הנה התוצאה שלנו: Tick Tick Tick Tick Tick Tick Tick Tick Tick השרשור נקטע. זה מסיים את ההקדמה שלנו עם Thread
השיטות החשובות ביותר של הכיתה. בהצלחה!
GO TO FULL VERSION