Threads (פתילים) ב JAVA

‫הרצאה מספר‪#09 :‬‬
‫‪Threads‬‬
‫תכנות ג'אווה ‪ +‬אינטרנט‬
‫‪JAVA & WEB PROGRAMMING‬‬
‫צבי מלמד‬
‫מכללת הדסה‪/‬מכללה חרדית‬
‫© כל הזכויות שמורות‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪1‬‬
‫הערה‬
‫•‬
‫•‬
‫•‬
‫•‬
‫מצגת זאת נכתבה עבור תלמידים שיודעים כבר היטב נושא של‬
‫פתילים ‪ threads‬בהיבט של מערכות הפעלה בכלל‪ ,‬ותכנות מערכת‬
‫כולל פתילים בלינוקס בפרט‪ ,‬ובכלל זה נושאים של סנכרון בין‬
‫תהליכים‪ ,‬שימוש בסמפורים‪ ,‬וכו'‬
‫המצגת מבוססת על פרק ‪ 26‬בספר של – ‪Deitel & Deitel: Java‬‬
‫‪How to program 9th Ed.‬‬
‫דוגמאות ההרצה אף הן מבוססות על הספר הזה‪.‬‬
‫מודגשים ההיבטים "הג'אווה‪-‬אים" של הנושא‪ ,‬כלומר הכלים ש‪-‬‬
‫‪ JAVA‬מספקת לנו בכדי לטפל בפתילים‪.‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪2‬‬
‫מבוא‬
‫• שפת ג'אווה מספקת מקביליות באמצעות השפה ובאמצעות ה ‪API‬‬
‫שלה‬
‫• המקביליות מושגת על ידי שימוש בפתילים – תהליכונים – ‪threads‬‬
‫• במחשבים מרובי מעבדים – הפתילים עשויים לרוץ על מעבדים‬
‫שונים‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪3‬‬
‫‪ Thread States‬מצבי הפתיל‬
‫•‬
‫‪.1‬‬
‫‪.2‬‬
‫‪.3‬‬
‫‪.4‬‬
‫‪.5‬‬
‫‪.6‬‬
‫בכל רגע נתון‪ ,‬כל פתיל יכול להיות באחד מהמצבים המתוארים‬
‫בתרשים הבא‪.‬‬
‫פתיל מתחיל את חייו במצב ‪ .new‬במצב זה עד שהוא "מתחיל"‬
‫לאחר שהתחיל‪/‬אותחל מוצב במצב ‪runnable‬‬
‫עובר למצב ‪ waiting‬כאשר מחכה לפתיל אחר לבצע משימה‬
‫כלשהי‬
‫ממצב ‪ runnable‬הוא יכול לעבור גם למצב ‪ time-waiting‬למשך‬
‫אינטרוול קצוב של זמן‪ .‬יוצא ממצב זה או כאשר נגמר הזמן או‬
‫שקרה אירוע שהפתיל המתין לו‬
‫פקודת )(‪ – sleep‬מעבירה למצב ‪ .time-waiting‬יוצא משם כעבור‬
‫פרק הזמן למצב ‪runnable‬‬
‫סיום המשימה של הפתיל (בעצמו או כהוראה ממישהו חיצוני‬
‫לפתיל) מעביר אותו למצב ‪terminated‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪4‬‬
‫‪ Thread States‬מצבי הפתיל‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪5‬‬
‫עוד על ‪ Thread States‬מצבי הפתיל‬
‫• מבחינת ‪ JAVA‬הפתיל במצב ‪runnable‬‬
‫• מבחינת מערכת ההפעלה הוא יכול להיות ‪ running‬או ‪ready‬‬
‫• העברתו למצב ‪ running‬נקראת ‪ dispatching the thread‬ומתבצעת‬
‫על ידי המודול במערכת ההפעלה שאחראי לזה‪ ,‬בד"כ ‪thread-‬‬
‫‪ scheduler‬או ‪ dispatcher‬שמעניק לפתיל ‪ quantum‬של זמן או‬
‫‪time-slice‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪6‬‬
‫עוד מבוא על פתילים ב‪JAVA -‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫לכל פתיל ב ‪ JAVA‬יש ‪ thread priority‬שמשפיע על קדימותו לקבל‬
‫זמן מעבד‬
‫אין בכך הבטחה או התחייבות באיזה סדר יזומנו הפתילים‬
‫מושגים שלמדנו במערכות הפעלה‪ ,‬כגון‪preemptive scheduling, :‬‬
‫‪starvation, aging (i.e. raising priority with age to prevent‬‬
‫)‪ – starvation‬כל אלו‪ ,‬תקפים ומתקיימים‪.‬‬
‫ההתנהגות של אפליקציות מרובות פתילים תהיינה שונה בין‬
‫מערכות ההפעלה‪ ,‬כתלות במימוש הפתילים במערכות השונות‬
‫מומלץ לא לייצר פתילים )‪ (thread objects‬במישרין‪ ,‬אלא להשתמש‬
‫בממשק ‪Executor‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪7‬‬
‫‪Creating and Executing Threads with‬‬
‫‪Executor Framework‬‬
‫• ‪ - A Runnable object‬משימה שיכולה להתבצע‪ ,‬לרוץ‪ ,‬במקביל‬
‫למשימות אחרות‬
‫• ‪ Runnable interface‬מגדיר מתודה בודדת‪ run :‬שמכילה את‬
‫הקוד שהפתיל צריך לבצע‪ .‬זאת בעצם פונקציית הכניסה של‬
‫הפתיל‪.‬‬
‫• בכדי לבצע ‪ thread‬מייצרים את האובייקט המתאים (כאמור‪ ,‬זהו‬
‫אובייקט שמממש את הממשק ‪ )runnable‬וקוראים למתודה ‪run‬‬
‫שמתבצעת בתוך תהליכון (פתיל) שונה‪ ,‬חדש‪.‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪8‬‬
‫‪Creating and Executing Threads with‬‬
‫‪ - Executor Framework‬דוגמא ‪26.3‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫בדוגמא זאת‪Class PrintTask implements Runnable :‬‬
‫מתודה סטטית )(‪ sleep‬של המחלקה ‪Thread‬‬
‫המתודה יכולה לזרוק חריג ‪ InterruptedException‬ולכן‬
‫הקריאה לה נמצאת בתוך ‪ try-catch‬מתאים‬
‫התוכנית מסתיימת כאשר הפתיל האחרון מסיים להתבצע‬
‫ה‪ Executor -‬מנהל מאגר פתילים ‪ thread pool‬ועי"כ יכול להשתמש‬
‫מחדש ‪ reuse‬בפתילים ולשפר מהירות‬
‫מעביר את האובייקט (שהוא ‪( )runnable‬ארגומנט של המתודה‬
‫‪ )execute‬לאחד הפתילים במאגר‪ .‬אם אין פתיל פנוי – יוצר חדש או‬
‫ממתין שאחד שרץ יסתיים‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪9‬‬
‫‪Creating and Executing Threads with‬‬
‫‪ - Executor Framework‬דוגמא ‪26.3‬‬
‫• הממשק ‪ ExecutorService‬יורש מ‪ Executor -‬ומגדיר‬
‫מתודות לניהול‪-‬שליטה במחזור החיים של ה‪Executor -‬‬
‫• יצירה על ידי קריאה למתודה הסטטית של המחלקה ‪Executors‬‬
‫בשם ‪ newCachedThreadPool‬שמחזירה‬
‫‪ ExecutorService‬שיוצרת פתילים חדשים שדרושים‬
‫לאפליקציה‬
‫• המתודה ‪ shutdown‬מודיעה ל‪ ExecutorService -‬להפסיק‬
‫לקבל משימות חדשות‪ ,‬אבל להמשיך לבצע את אלו שכבר נשלחו‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪15‬‬
‫סנכרון פתילים‬
‫• כאמור בהערת המבוא ‪ -‬הרקע לנושא זה לא מכוסה כאן‪...‬‬
‫• המטרה לאפשר גישה אקסקלוסיבית לקוד שמטפל באובייקט‬
‫משותף‪ ,‬כלומר להשיג ‪mutual exclusion‬‬
‫• המטרה מושגת באמצעות ‪monitors‬‬
‫– לכל אובייקט יש ‪ monitor‬ו‪ monitor lock -‬שיכולים להיות‬
‫מוחזקים רק בידי פתיל אחד בכל רגע נתון‪.‬‬
‫– הפתיל צריך לקבל‪-‬להשיג ‪ acquire‬את המנעול לפני שהוא‬
‫ממשיך בפעולתו‪ .‬אם המוניטור לא פנוי‪ ,‬הוא נחסם ‪blocked‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪16‬‬
‫סנכרון פתילים‬
‫• כיצד משיגים את האפקט הזה?‬
‫– על ידי שמקדימה לפקודה (פקודות) את המילה ‪synchronized‬‬
‫) ‪– synchronized ( object‬‬
‫{‬
‫‪statements‬‬
‫‪} // end synchronized statement‬‬
‫– כאשר ‪ object‬הוא האובייקט שלגביו רוצים להפעיל את ה‪monitor -‬‬
‫‪ ,lock‬בדרך כלל זה יהיה האובייקט ‪this‬‬
‫– אבל אם נרצה באובייקט‪/‬מחלקה ‪ A‬לדאוג שקטע קוד יסונכרן עבור‬
‫אובייקט אחר ‪ ,B1‬אזי נוכל בתוך המחלקה ‪ A‬לכתוב‬
‫– לחליפין‪ ,‬אפשר להגדיר מתודה שלמה כ ‪synchronized‬‬
‫• הערה‪ :‬הנעילה איננה על קטע קוד מסוים‪ ,‬אלא על כל קטעי הקוד של‬
‫אותו אובייקט‪.‬‬
‫– כלומר‪ :‬אם נעלנו קטע קוד מסוים של אובייקט ‪ ,X‬אזי בו זמנית לא‬
‫ניתן לבצע מתודה אחרת של אותו אובייקט (או קטע קוד אחר)‬
‫שמסומן ב ‪synchronized‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪17‬‬
‫סנכרון פתילים ‪ -‬הבהרות‬
‫•‬
‫•‬
‫אם נרצה באובייקט של מחלקה ‪ A‬לדאוג שקטע קוד יסונכרן עבור אובייקט אחר‬
‫‪ b1‬של מחלקה ‪ ,B‬אזי נוכל בתוך המחלקה ‪ A‬לכתוב‪:‬‬
‫;)(‪B b1 = new B‬‬
‫)‪synchronized ( b1‬‬
‫{‬
‫‪specific statements to synchronize‬‬
‫‪} // end synchronized statement‬‬
‫קטע הקוד הזה יצטרף לכלל הקטעים שהם בסנכרון עבור האובייקט ‪ ,b1‬כלומר‬
‫אם הגדרנו במחלקה ‪ B‬מספר מתודות שהן ‪ synchronized‬ברמת המתודה‪ ,‬אזי‬
‫כל המתודות האלו‪ ,‬וגם הקטע הספציפי הזה‪ ,‬כולם נכנסים למנגנון הנעילה‪..‬‬
‫כלומר בו‪-‬זמנית הם מתבצעים לכל היותר על ידי יותר פתיל אחד‪.‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪18‬‬

‫מממשת את‬ArrayWriter (Fig. 26.6) ‫המחלקה‬
interface Runnable to define a task for ‫הממשק‬
inserting values in a SimpleArray object.
The task completes after three consecutive integers
beginning with startValue are added to the
SimpleArray object.
‫דרוש עיבוד‬
© Copyright 1992-2012 by Pearson
Education, Inc. All Rights Reserved.




Class SharedArrayTest (Fig. 26.7) executes two
ArrayWriter tasks that add values to a single
SimpleArray object.
ExecutorService’s shutDown method prevents additional
tasks from starting and to enable the application to terminate
when the currently executing tasks complete execution.
We’d like to output the SimpleArray object to show you the
results after the threads complete their tasks.
◦ So, we need the program to wait for the threads to complete before
main outputs the SimpleArray object’s contents.
◦ Interface ExecutorService provides the awaitTermination
method for this purpose—returns control to its caller either when all
tasks executing in the ExecutorService complete or when the
specified timeout elapses.
© Copyright 1992-2012 by Pearson
Education, Inc. All Rights Reserved.
‫יצירת פעולות אטומיות‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫הפלט בדוגמא הקודמת הוא "מבולבל" או לא נכון‪ ...‬בגלל‬
‫שהפתילים אינם ‪thread safe‬‬
‫"דורכים ומקלקלים" את הערך של המשתנה ‪writeIndex‬‬
‫צריך שפעולה העדכון והכתיבה של המערך ושל האינדקס שלו‪ ,‬יהיו‬
‫פעולות אטומיות‬
‫כאמור לעיל‪ ,‬האטומיות ניתנת להשגה על ידי שימוש במילת‬
‫המפתח ‪synchronized‬‬
‫הדוגמא ‪ 26.8‬ממחישה שימוש במחלקה ‪ simpleArray‬בדוגמא‬
‫הקודמת‪ ,‬אלא שכאן דואגים לסינכרון‪.‬‬
‫שימו לב שלצורך הדוגמא הדפסות פלט נמצאות בתוך‬
‫‪ .synchronized‬זה טוב להדגמות ורע מאוד לתכנה אמתית‪.‬‬
‫(מדוע??)‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪28‬‬
‫יצירת פעולות אטומיות‬
‫• כללים בהקשר זה‪:‬‬
‫– קטעי הקוד שהם בתוך ‪ synchronized‬צריכים להיות קצרים‬
‫ומהירים בכדי לעקב במידה מינימלית פתילים שממתינים‬
‫למנעול‬
‫– מנעול דרוש רק עבור ‪shared mutable data‬‬
‫– אם המידע המשותף בין הפתילים איננו ‪ mutable‬לא דרוש‬
‫מנעול‪ ...‬אבל‪..‬‬
‫– אבל מומלץ להשתמש ב‪ final -‬בכדי לחדד את העובדה שלא‬
‫דרוש מנעול‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪33‬‬
‫דוגמא – ‪producer-consumer‬‬
‫• היצרן והצרכן כותבים לתוך ‪ Buffer‬חוצץ‬
‫• כדאי – מומלץ – להשתמש בממשק ‪ – interface‬להלן‪:‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪34‬‬
‫היצרן – המחלקה ‪Producer‬‬
‫הקונסטרקטור מקבל ארגומנט‪:‬‬
‫את ה‪ buffer -‬שלתוכו יכתוב‬
‫היצרן – המחלקה ‪Producer‬‬
‫ישן זמן אקראי ואז כותב לתוך‬
‫ה‪ sharedLocation -‬את‬
‫המספר ‪count‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪36‬‬
‫הצרכן– המחלקה ‪Consumer‬‬
‫הקונסטרקטור מקבל ארגומנט‪:‬‬
‫את ה‪ buffer -‬שממנו יקרא‬
UnsynchronizedBuffer ‫המחלקה‬
SharedBufferTest ‫המחלקה‬
‫סינכרון באמצעות ‪ArrayBlockingQueue-‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫ל‪ JAVA-‬יש ‪ – concurrency package‬מארז מחלקות‪/‬פתרונות‬
‫לבעיות מקביליות‬
‫‪java.util.concurrent‬‬
‫המחלקה ‪ ArrayBlockingQueue‬היא ‪thread-safe‬‬
‫שמממשת את הממשק ‪BlockingQueue‬‬
‫מספקת מתודות ‪ put‬ו‪take -‬‬
‫– ‪ Put‬שמה אלמנט בסוף התור‪ .‬ממתינה אם התור מלא‬
‫– ‪ Take‬לוקחת‪/‬מורידה אלמנט‪ .‬ממתינה אם התור ריק‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪47‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪52‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪53‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪54‬‬
‫סנכרון בעצמנו‪...‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫על ידי שימוש ב‪ synchronized -‬ובמתודות של ‪Object‬‬
‫כותבים ‪ get, set‬תוך שימוש ב ‪synchronized‬‬
‫המתודות של ‪ – wait, notify, notifyAll :Object‬משמשות לסינכרון‬
‫ההמתנה‬
‫– )(‪ Wait‬על אובייקט עם מסונכרן – משחרר את המנעול‪ ,‬ומעביר‬
‫את הקורא למצב ‪waiting state‬‬
‫– )(‪ – notify‬מעביר תהליך אחד מהממתינים למצב ‪runnable‬‬
‫– )(‪ – notifyAll‬מעביר את כל התהליכים שממתינים למצב‬
‫‪ – runnable‬כלומר הם יכולים לבקש את המנעול‬
‫שגיאה‪ :‬אסור לקרוא למתודות הנ"ל מבלי שקודם כל דאגנו לקבל‬
‫את המנעול! זה יגרום לחריגה ‪IllegalMonitorStateException‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪55‬‬
‫סנכרון – חוצץ ציקלי‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫הבעיה בדוגמא הקודמת – אם הייצרן והצרכן אינם עובדים באותו‬
‫קצב – הם מאיטים אחד את השני (בכל פעם אחד מהם מחכה‬
‫לשני)‬
‫פתרון אפשרי (שהכרנו בעבר) – חוצץ שמכיל מספר תאים לשמירת‬
‫המוצרים שהיצרן ייצר‬
‫ניתן להשתמש ב‪ ArrayBlockingQueue -‬והמחלקה דואגת‬
‫לכל פרטי הסנכרון‬
‫אפשר לממש בעצמנו חוצץ ציקלי – ‪ - CircularBuffer‬ראו‬
‫דוגמא ‪26.18-19‬‬
‫הדוגמא איננה מובאת בשקפים אלו‪.‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪65‬‬
‫‪The Lock and Condition Interfaces‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫לכל האובייקטים שרוצים להסתנכרן על מנעול מסוים‪ ,‬יש ‪reference‬‬
‫לאובייקט שמממש את הממשק ‪Lock‬‬
‫קריאה למתודות ‪lock or unlock‬‬
‫אם מספר פתילים מנסים לקרוא ל‪ lock -‬בו זמנית‪ ,‬מובטח שרק‬
‫אחד מהם יצליח והאחרים יכנסו למצב ‪ waiting state‬עבור המנעול‬
‫הזה‬
‫קריאה ל‪ unlock -‬משחררת את המנעול‪ ,‬ואחד הפתילים שנמצא‬
‫בהמתנה מקבל אותו וממשיך לרוץ (עובר למצב ריצה)‬
‫הממשק ממומש על ידי המחלקה ‪ReentrantLock‬‬
‫הקונסטרקטור מקבל ארגומנט בוליאני האם להפעיל ‪fairness‬‬
‫‪ .policy‬אם ‪ true‬אזי מופעלת המדיניות – "הפתיל שמחכה הכי‬
‫הרבה זמן‪ ,‬הוא זה שיקבל את המנעול ברגע שהמנעול יתפנה"‬
‫– נחמד להיות הוגן‪ ...‬אבל פוגע בביצועים לעומת העדר הוגנות‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪66‬‬
‫‪The Lock and Condition Interfaces‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫פתיל שתפש מנעול‪ ,‬עשוי לגלות שהוא לא יכול להמשיך בפעולתו‪ ,‬עד‬
‫שמתקיים תנאי מסוים (למשל‪ ,‬הצרכן‪ ,‬עד שיש מוצרים לצרוך בחוצץ)‪.‬‬
‫ניתן להמתין על ‪condition object‬‬
‫זהו אובייקט שמממש את הממשק ‪Condition‬‬
‫‪ Condition object‬משויכים למנעול מסוים‪ ,‬ספציפי‪.‬‬
‫יוצרים אותם על ידי קריאה למתודה ‪ newCondition‬של המנעול‬
‫הספציפי‬
‫לאחר שיצרנו את האובייקט‪ ,‬בכדי להמתין נקרא למתודה ‪ await‬שלו‬
‫– מידית משחרר את המנעול‬
‫– שם את הפתיל בתור הממתינים עבור ה‪ condition -‬הזה‬
‫פתיל אחר‪ ,‬שרץ‪ ,‬יבצע קריאה ‪ signal‬או ‪ signalAll‬של ה‪-‬‬
‫‪ condition‬הזה ובהתאמה פתיל אחד מהממתינים‪ ,‬או כולם יעברו למצב‬
‫‪runnable‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪67‬‬
‫‪The Lock and Condition Interfaces‬‬
‫– הערות‬
‫• עלינו להבטיח שאיננו יוצרים ‪deadlock‬‬
‫• יש להציב קריאות ל‪ unlock-‬בתוך בלוק ‪ !finally‬עי"כ אם‬
‫קורית חריגה‪ ,‬הבלוק עדיין מתבצע והמנעול ישתחרר‪.‬‬
‫• אם לא נעשה כן‪ ...‬ותקרה חריגה אזי "המנעול ימות"‬
‫• דוגמא‪Fig_26_20-21 :‬‬
‫‪ 30‬דצמבר ‪12‬‬
‫© צבי מלמד – כל הזכויות שמורות‬
‫‪68‬‬