מבוא לשרשורי Java

מאמר זה, אחד הראשונים שפורסמו אי פעם על ידי JavaWorld, מתאר כיצד מיושמים שרשורים בשפת התכנות של Java, החל בסקירה כללית של שרשורים.

במילים פשוטות, חוט הוא מסלול הביצוע של התוכנית. מרבית התוכניות שנכתבות כיום פועלות כחוט אחד, וגורמות לבעיות כאשר מספר אירועים או פעולות צריכים להתרחש בו זמנית. נניח, למשל, תוכנית אינה מסוגלת לצייר תמונות בזמן קריאת הקשות. על התוכנית לתת את מלוא תשומת הלב לקלט המקלדת החסר יכולת לטפל ביותר מאירוע אחד בכל פעם. הפיתרון האידיאלי לבעיה זו הוא ביצוע חלק של שני חלקים או יותר של תוכנית בו זמנית. האשכולות מאפשרים לנו לעשות זאת.

לימוד על נושאי Java

מאמר זה הוא חלק מארכיון התוכן הטכני JavaWorld. עיין בהמשך כדי ללמוד עוד על נושאי Java ובמקביל:

הבנת נושאי Java ( סדרת Java 101 , 2002):

  • חלק 1: הצגת חוטים ורצים
  • חלק 2: סנכרון חוטים
  • חלק 3: תזמון חוטים והמתנה / הודעה
  • חלק 4: קבוצות חוטים ותנודתיות

מאמרים קשורים

  • Java עם הברגה היפר: שימוש ב- API של Java Concurrency API (2006)
  • צגים טובים יותר לתוכניות מרובי הליכות (2007)
  • הבנת מקבילות השחקנים, חלק 1 (2009)
  • איתור וטיפול בחוט תלוי (2011)

כמו כן לבדוק את JavaWorld מפת האתר ואת מנוע החיפוש .

יישומים מרובי הברגה מספקים את כוחם החזק על ידי הפעלת שרשורים רבים בו זמנית בתוך תוכנית אחת. מנקודת מבט לוגית, multithreading פירושו שניתן להפעיל מספר שורות של תוכנית אחת בו זמנית, עם זאת, זה לא זהה להפעלת תוכנית פעמיים ולומר שיש מספר שורות של תוכנית שמבוצעות באותו זמן. זְמַן. במקרה זה, מערכת ההפעלה מתייחסת לתוכניות כשני תהליכים נפרדים ונבדלים. תחת יוניקס, זיוף תהליך יוצר תהליך ילד עם מרחב כתובות שונה עבור קוד ונתונים כאחד. למרות זאת,fork()יוצר תקורה רבה עבור מערכת ההפעלה, מה שהופך אותה לפעולה עתירת מעבד. על ידי הפעלת שרשור במקום, נוצר נתיב ביצוע יעיל תוך שיתוף אזור הנתונים המקורי מההורה. הרעיון של שיתוף אזור הנתונים מועיל מאוד, אך מעלה כמה תחומי דאגה עליהם נדבר בהמשך.

יצירת חוטים

היוצרים של ג'אווה תכננו באדיבות שתי דרכים ליצירת שרשורים: הטמעת ממשק והרחבת כיתה. הרחבת כיתה היא הדרך בה ג'אווה יורשת שיטות ומשתנים ממעמד אב. במקרה זה, ניתן להרחיב או לרשת רק מכיתת הורה יחיד. ניתן להתגבר על מגבלה זו בתוך Java באמצעות יישום ממשקים, שהיא הדרך הנפוצה ביותר ליצור נושאים. (שימו לב כי פעולת הירושה מאפשרת רק להפעיל את הכיתה כחוט. זה תלוי בכיתה start()לביצוע וכו ').

ממשקים מספקים דרך למתכנתים להניח את הבסיס של הכיתה. הם משמשים לעיצוב הדרישות לקבוצת שיעורים ליישום. הממשק מגדיר הכל, והכיתה או הכיתות שמיישמים את הממשק עושים את כל העבודה. מערכת המחלקות השונה המיישמת את הממשק צריכה לעמוד באותם כללים.

ישנם כמה הבדלים בין כיתה לממשק. ראשית, ממשק יכול להכיל רק שיטות מופשטות ו / או משתנים סופיים סטטיים (קבועים). לעומת זאת, הכיתות יכולות ליישם שיטות ולהכיל משתנים שאינם קבועים. שנית, ממשק אינו יכול ליישם שום שיטה. מחלקה המיישמת ממשק חייבת ליישם את כל השיטות המוגדרות בממשק זה. לממשק יכולת להאריך מממשקים אחרים, ובניגוד למחלקות יכול להאריך ממספר ממשקים. יתר על כן, ממשק לא יכול להיות מיוצר עם המפעיל החדש; למשל, Runnable a=new Runnable();אסור.

השיטה הראשונה ליצירת שרשור היא פשוט להאריך Threadמהכיתה. עשה זאת רק אם אין צורך להרחיב את הכיתה שאתה זקוק להורג כשרשור ממעמד אחר. Threadבכיתה מוגדרת בחבילה java.lang, אשר צריך להיות מיובאות כך הכיתות שלנו מודעות הגדרתה.

import java.lang.*; public class Counter extends Thread { public void run() { .... } }

הדוגמה לעיל יוצרת מחלקה חדשה Counterהמרחיבה את Threadהמחלקה ועוקפת את Thread.run()השיטה ליישום שלה. run()השיטה שבה כל העבודה של Counterפתיל בכיתה נעשתה. ניתן ליצור את אותה מחלקה על ידי יישום Runnable:

import java.lang.*; public class Counter implements Runnable { Thread T; public void run() { .... } }

כאן, run()השיטה המופשטת מוגדרת בממשק Runnable והיא מיושמת. שים לב שיש לנו מופע של Threadהמחלקה כמשתנה של Counterהמחלקה. ההבדל היחיד בין שתי השיטות הוא שבאמצעות יישום Runnable, קיימת גמישות רבה יותר ביצירת הכיתה Counter. בדוגמה שלעיל, עדיין קיימת ההזדמנות להרחיב את Counterהכיתה, במידת הצורך. רוב הכיתות שנוצרו שיש להריץ כחוט יישמו את Runnable מכיוון שהן כנראה מרחיבות פונקציונליות אחרת ממחלקה אחרת.

אל תחשוב שממשק ה- Runnable מבצע עבודה אמיתית כאשר הברגה מבוצעת. זה רק כיתה שנוצרה כדי לתת מושג על עיצוב Threadהכיתה. למעשה, הוא קטן מאוד המכיל שיטה מופשטת אחת בלבד. להלן ההגדרה של ממשק Runnable ישירות ממקור Java:

package java.lang; public interface Runnable { public abstract void run(); }

זה כל מה שיש לממשק Runnable. ממשק מספק רק תכנון לפיו יש ליישם שיעורים. במקרה של ממשק Runnable, הוא מכריח את הגדרת run()השיטה בלבד . לכן, רוב העבודה נעשית Threadבכיתה. מבט מקרוב על חלק בהגדרת Threadהכיתה ייתן מושג מה באמת קורה:

public class Thread implements Runnable { ... public void run() { if (target != null) { target.run(); } } ... }

מקטע הקוד שלעיל ניכר כי מחלקת Thread מיישמת גם את ממשק ה- Runnable. Thread. run()בודק כדי לוודא שמחלקת היעד (המחלקה שעומדת להתנהל כחוט) אינה שווה ל- null ואז מבצע את run()שיטת היעד. כאשר זה קורה, run()שיטת היעד תפעל כחוט משלה.

מתחילים ועוצרים

מכיוון שכעת ניכרות הדרכים השונות ליצירת מופע של שרשור, נדון ביישום שרשורים המתחילים בדרכים הזמינות להפעלה ולעצור אותם באמצעות יישומון קטן המכיל שרשור להמחשת המכניקה:

דוגמא CounterThread וקוד מקור

היישומון הנ"ל יתחיל לספור מ- 0 ויציג את הפלט שלו גם למסך וגם למסוף. מבט חטוף עשוי ליצור את הרושם כי התוכנית תתחיל לספור ותציג כל מספר, אך זה לא המקרה. בחינה מדוקדקת יותר של ביצוע היישומון הזה תגלה את זהותו האמיתית.

במקרה זה, CounterThreadהכיתה נאלצה ליישם את Runnable מאחר שהרחיבה את היישומון הכיתתי. כמו בכל היישומונים, init()השיטה מבוצעת תחילה. ב- init(), המשתנה ספירה מאותחל לאפס Threadונוצר מופע חדש של המחלקה. מאחר שעבר thisאת Threadהבנאי, החוט החדש ידע איזה אובייקט לרוץ. במקרה זה thisהיא התייחסות ל CounterThread. לאחר יצירת השרשור הוא צריך להתחיל. הקריאה אל start()תקרא run()לשיטת היעד , כלומר CounterThread. run(). הקריאה אל start()תחזור מיד והשרשור יתחיל לבצע במקביל. שימו לב run()שהשיטה היא לולאה אינסופית. זה אינסופי כי פעם הrun()כאשר השיטה יוצאת, השרשור מפסיק לבצע. run()שיטת תגדיל את מספר משתנה, שינה במשך 10 אלפיות ולשלוח בקשה לרענן את התצוגה של היישומון.

שימו לב שחשוב לישון איפשהו בחוט. אם לא, השרשור יכלה את כל זמן המעבד לתהליך ולא יאפשר לבצע שיטות אחרות כגון שרשור. דרך נוספת להפסיק את ביצוע השרשור היא לקרוא stop()לשיטה. בדוגמה זו, הברגה נעצרת כאשר לוחצים על העכבר בזמן שהסמן נמצא ביישומון. בהתאם למהירות המחשב שעליו פועל היישומון, לא כל מספר יוצג, מכיוון שהתוספת מתבצעת ללא תלות בציור היישומון. לא ניתן לרענן את היישומון בכל בקשה, ולכן מערכת ההפעלה תעמוד בתור לבקשות ובקשות רענון עוקבות יסתפקו ברענון אחד. בזמן שהרענונים עומדים בתור, הרוזן עדיין גדל אך לא מוצג.

השעיה וחידוש

לאחר הפסקת שרשור, לא ניתן להפעיל אותו מחדש באמצעות start()הפקודה, מכיוון stop()שיסיים את ביצוע השרשור. במקום זאת תוכלו להשהות את ביצוע השרשור באמצעות sleep()השיטה. השרשור ישן לפרק זמן מסוים ואז יתחיל לבצע עם הגבלת הזמן. אבל, זה לא אידיאלי אם צריך להתחיל את השרשור כאשר מתרחש אירוע מסוים. במקרה זה, suspend()השיטה מאפשרת להפסיק לבצע באופן זמני resume()את השרשור והשיטה מאפשרת לשרשור המושעה להתחיל מחדש. היישומון הבא מציג את הדוגמה לעיל ששונתה כדי להשעות ולחדש את היישומון.

public class CounterThread2 extends Applet implements Runnable { Thread t; int Count; boolean suspended; public boolean mouseDown(Event e,int x, int y) { if(suspended) t.resume(); else t.suspend(); suspended = !suspended; return true; } ... }

CounterThread2 דוגמה וקוד מקור

כדי לעקוב אחר המצב הנוכחי של היישומון, משתמשים במשתנה הבוליאני suspended. הבחנה במצבים השונים של יישומון חשובה מכיוון ששיטות מסוימות יזרקו חריגים אם הם נקראים במצב לא נכון. לדוגמא, אם היישומון הופעל ונעצר, ביצוע start()השיטה יביא IllegalThreadStateExceptionחריג.