Presentation is loading. Please wait.

Presentation is loading. Please wait.

Java Threads אליהו חלסצ'י תכנות מתקדם תרגול מספר 6

Similar presentations


Presentation on theme: "Java Threads אליהו חלסצ'י תכנות מתקדם תרגול מספר 6"— Presentation transcript:

1 Java Threads אליהו חלסצ'י תכנות מתקדם 89-210 תרגול מספר 6
תשס"ט Java Threads אליהו חלסצ'י

2 האם המלבנים זזים בו זמנית ?

3 הקדמה הגדרה: thread (או תהליכון בעברית) הוא קטע יחיד ורציף של פעולות עם התחלה, רצף וסוף. מערכות הפעלה מודרניות מאפשרות לתהליך (process), להריץ מספר תהליכונים (threads) במרחב כתובות אחד. אנו יכולים לשנות קוד כך שירוץ בחלקים, ותיווצר תחושת מקביליות, אך הדבר כמובן מסורבל. כפיתרון שפת ה java מאפשרת לתכנת ב threads די בקלות – הקוד נכתב כרגיל ופשוט מורץ ב thread אחר.

4 על תהליכים ותהליכונים מערכת ההפעלה יכולה להריץ כמה תהליכים שונים במקביל, כל אחד במרחב כתובות משלו. בדרך כלל יש יותר תהליכים מאשר מעבדים (או ליבות) ולכן המעבד צריך לשלב בין התהליכים ברמה שתיראה לנו שהם רצים במקביל. (Time Slicing) לשם כך מתבצע מידי פעם מעבר במעבד מתהליך אחד לאחר, הפעולה נקראת Context Switch, היא יקרה מאד, ותלמדו על כל זאת ועוד בקורס מערכות הפעלה. יתרון בלתכנת בכמה תהליכים הוא לכאורה קבלת יותר משאבים ממערכת ההפעלה. יתרון אמיתי יותר הוא שאם תהליך נתקע, התוכנה כולה לא קורסת.

5 על תהליכים ותהליכונים כעת, כל תהליך שרץ במערת ההפעלה יכול להריץ כמה threads –תהליכונים. תהליכונים אלו הם חלקים שרצים "במקביל" כחלק מהתהליך עצמו ובמרחב הכתובות שלו. המשמעות שהתהליכונים שותפים לאותו מרחב כתובות היא שניתן בקלות להעביר מידע ביניהם. (יותר בקלות מאשר בין תהליכים) כמו כן, המעבר בין תהליכון אחד לשני אינו יקר כמו context switch בין תהליך אחד לשני. החיסרון הוא כמובן שיש לנו הקצאות זמן עיבוד של תהליך אחד.

6 Process 5 Process 6 Process 7 Process 8 Process 1 Process 2 Process 3
Operating System Context Switch Process 1 Process 2 Process 3 Process 4 thread1 thread2 thread1 thread2 VM thread3 ב VM ובמערוכת הפעלה מתקדמות ניתן אפילו להריץ threads שונים בליבות שונות אך כיום במערכות הסטנדרטיות אין אפילו ניצול מוחלט של הליבות לתהליכים.

7 למה צריך threads? יש אלגוריתמים מקביליים.
I/O זו פעולה שלוקחת המון זמן מנקודת המבט של המעבד, למה לא לאפשר ריצה של דברים אחרים בינתיים? ביצוע משימות בלתי תלויות. Timers למיניהם. הכנה לסביבה מרובת מעבדים \ ליבות. עבודות שצריכות לרוץ ברקע, כמו gc.

8 יצירת threads כדי להריץ threads אנו צריכים להכיר את שני הדברים הבאים:
הממשק Runnable המחלקה Thread שמממשת את Runnable. ל Runnable יש רק פונקציה אחת void run(); למחלקה Thread יש פונקציה מיוחדת start(); שהיא מריצה את run() בתהליכון נפרד, ומייד חוזרת – כלומר רצף התוכנית נמשך מייד לאחר הקריאה ל start(), ואין המתנה שהפעולה תסתיים. Runnable void run() Thread void run() void start()

9 יצירת threads הדרך ליצור תוכנית שתרוץ בthread נפרד: לרשת מ Thread
לשכתב את הפונקציה run() שתעשה את הפעולות שנרצו שיקרו במקביל. לקרוא ל start(); class MyClass extends Thread { public void run() { קוד שירוץ במקביל... } MyClass m = new MyClass() ; m.start() ; כדאי לשאול אותם, מה עוד לא בסדר בשיטה זו? ערבבנו בין ה"מה" וה"איך" בין ה Thread והלוגיקה של מה לבצע ולבין אופן הביצוע עצמו... שאלה: אבל מה אם המחלקה שלי צריכה לרשת כבר ממחלקה אחרת?

10 יצירת threads במקרה זה נצטרך: לממש את Runnable
ליצור מופע חדש של המחלקה Thread עם המחלקה שלנו ב constructor לקרוא ל start(); class MyClass implements Runnable { public void run() { קוד שירוץ במקביל... } MyClass m = new MyClass() ; Thread t = new Thread(m) ; t.start() ; שאלה: אבל מה קורה אם אנחנו לא רוצים לגעת במחלקה המקורית או לא יכולים?

11 יצירת threads נניח שיש לנו מחלקה C עם המתודה m שאנו לא רוצים \ יכולים לשנות בה כלום. אנו רוצים שהמתודה m תרוץ ב thread נפרד. נפתור זאת ע"י ירושה, או ע"י design pattern בשם Adapter. ע"י ירושה: class C { public void m() { …} } class RunnableC extends C implements Runnable { public void run() { m() ; } } RunnableC rc = new RunnableC() ; Thread t = new Thread(rc) ; t.start() ;

12 יצירת threads נניח שיש לנו מחלקה C עם המתודה m שאנו לא רוצים \ יכולים לשנות בה כלום. אנו רוצים שהמתודה m תרוץ ב thread נפרד. נפתור זאת ע"י ירושה, או ע"י design pattern בשם Adapter. ע"י Adapter: class C { public void m() { …} } class AdapterOfC implements Runnable { private C c ; public AdapterOfC(C c) { this.c = c ; } public void run() { c.m() ; } } C myC=new C(); AdapterOfC aoc = new AdapterOfC(myC) ; Thread t = new Thread(aoc) ; t.start() ;

13 מתודות שליטה ב threads start – מתחילה להריץ את run ב thread נפרד.
sleep – ה thread נעצר עד שמתבצעת קריאה ל interrupt – עוצר את שנת ה thread. join – מחכה שה thread יסיים את עבודתו. קראנו ל t.start() שעושה את שלו. המשכנו לעשות דברים במקביל ל t. קריאה ל t.join() תגרום לנו לחכות ש t יסתיים. get/setPriority קביעת עדיפות הריצה של ה thread. yield - מאפשר ל threads אחרים בעלי אותה העדיפות זמן CPU על חשבונו. isAlive – האם ה thread עדיין לא סיים את עבודתו. Obj.wait() – גורם ל thread הנוכחי להמתין עד שthread אחר יקרא ל notify Obj.notify() – מעיר את אחד ה threads הממתינים.

14 מחזור החיים של thread born blocked פעולת synchronized או המתנה ל I/O
סוף הסנכון או ה I/O running בחירה של ה scheduler born מצב ready בתור start() dead stop() run() סיים סוף ה time-slice או קריאה ל yield waiting notify() wait() sleeping sleep() interrupt()

15 מצב deadlock נוצר כאשר שני threads ממתינים כל אחד שהשני "יעיר" אותו.
בד"כ קורה כשכל אחד מחכה למשאב כלשהו מהשני... כדי להימנע ממקרה זה, יש לשמור על שני כללים פשוטים: החלטה על סדר הנעילות הגיוני. שחרור הנעילות בסדר הפוך.

16 הבעיה ב threads מה יהיה הפלט? לכאורה count גדל 100,000,000 פעמים ב 1
public class ThreadTest { static int count=0; private static class A implements Runnable{ public void run(){ for(int i=0;i< ;i++) count++; } public static void main(String[] args) { Thread t=new Thread(new A()); Thread t1=new Thread(new A()); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(count); מה יהיה הפלט? לכאורה count גדל 100,000,000 פעמים ב 1 בשני threads שונים ולכן התוצאה צריכה להיות 200,000,000... הפלט שיצא הפעם: 158,123,608 בעיה זו שייכת לבעיית הקטע הקריטי בפועל הפקודה count++ היא 3 פקודות שונות: טעינה מהזיכרון ערך לרגיסטר פעולה חשבונית על הרגיסטר שמירת הערך בזיכרון

17 סנכרון הפתרון לבעיה מושג ע"י סנכרון.
המילה synchronized יכולה להופיע בכמה מקומות בקוד, ולאפשר גישה אקסקלוסיבית ל thread אחד בלבד באותו קטע קוד. כעת נשכלל את הדוגמא הקודמת: ניצור מחלקה כללית שתטפל בספירה. ניצור לה adapter שנוכל להכניס ל thread ושם נכניס את הלולאה ל 100,000,000 עדכונים

18 סנכרון על אובייקט עדיין לא סנכרנו דבר, ותוצאה לדוגמא היא : 131733427
public class ThreadTest { private static class CountAdapter implements Runnable{ Count c; public CountAdapter(Count c){ this.c=c; } public void run(){ for(int i=0;i< ;i++) c.update(); public static void main(String[] args) { Count c=new Count(); c.setCount(0); CountAdapter ca=new CountAdapter(c); Thread t=new Thread(ca); Thread t1=new Thread(ca); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(c.getCount()); public class Count { private int count; public void setCount(int x){count=x;} public int getCount(){return count;} public void update(){count++;} } עדיין לא סנכרנו דבר, ותוצאה לדוגמא היא : הוספת synchronized על מתודה כלשהי תחסום את גישת האובייקט כולו ל threads אחרים עד שהמתודה תסתיים. שני threads על אותו האובייקט, מתאפשרת חלוקת מידע.

19 סנכרון על אובייקט public class ThreadTest { private static class CountAdapter implements Runnable{ Count c; public CountAdapter(Count c){ this.c=c; } public void run(){ for(int i=0;i< ;i++) c.update(); public static void main(String[] args) { Count c=new Count(); c.setCount(0); CountAdapter ca=new CountAdapter(c); Thread t=new Thread(ca); Thread t1=new Thread(ca); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(c.getCount()); public class Count { private int count; public void setCount(int x){count=x;} public int getCount(){return count;} public synchronized void update(){count++;} } הוספנו לפקודה update סנכרון והפעם התוכנית רצה מאד מאד לאט, מדוע? היכן יותר כדאי לשים את הסנכרון?

20 סנכרון על אובייקט עכשיו אכן התקבלה התוצאה הרצויה,
public class ThreadTest { private static class CountAdapter implements Runnable{ Count c; public CountAdapter(Count c){ this.c=c; } public synchronized void run(){ for(int i=0;i< ;i++) c.update(); public static void main(String[] args) { Count c=new Count(); c.setCount(0); CountAdapter ca=new CountAdapter(c); Thread t=new Thread(ca); Thread t1=new Thread(ca); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(c.getCount()); public class Count { private int count; public void setCount(int x){count=x;} public int getCount(){return count;} public void update(){count++;} } עכשיו אכן התקבלה התוצאה הרצויה, אך האם שני ה threads אכן רצו במקביל? התשובה היא לא! רעיונות?

21 סנכרון על אובייקט עכשיו התקבלה התוצאה הרצויה,
public class Count { private int count; public void setCount(int x){count=x;} public int getCount(){return count;} public void update(){count++;} } public class ThreadTest { private static class CountAdapter implements Runnable{ public void run(){ for(int j=0;j<10000;j++) synchronized(c){ for(int i=0;i<10000;i++) c.update(); } public static void main(String[] args) { עכשיו התקבלה התוצאה הרצויה, גם יצרנו מקביליות בין 2 ה threads וגם התוכנית פעלה מהר!

22 סנכרון על מחלקה דוגמא חדשה, הפעם מפעילים את Count בצורה סטטית.
public class Count { private static int count; public static void setCount(int x){count=x;} public static int getCount(){return count;} public static void update(){count++;} } public class ThreadTest { private static class CountRunnable implements Runnable{ public void run(){ for(int i=0;i< ;i++) Count.update(); } public static void main(String[] args) { CountRunnable ca=new CountRunnable(); Thread t=new Thread(ca); Thread t1=new Thread(ca); Count.setCount(0); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(Count.getCount()); דוגמא חדשה, הפעם מפעילים את Count בצורה סטטית. עדיין לא סנכרנו דבר, ותוצאה לדוגמא היא :

23 סנכרון על מחלקה public class Count { private static int count; public static void setCount(int x){count=x;} public static int getCount(){return count;} public static synchronized void update(){count++;} } public class ThreadTest { private static class CountRunnable implements Runnable{ public void run(){ for(int i=0;i< ;i++) Count.update(); } public static void main(String[] args) { CountRunnable ca=new CountRunnable(); Thread t=new Thread(ca); Thread t1=new Thread(ca); Count.setCount(0); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(Count.getCount()); אפשרות ראשונה, לשים synchronized על המתודה הסטטית, כל גישה סטטית תסונכרן. מה הבעיה באפשרות זו? מה נעשה הפעם כדי לפתור?

24 סנכרון על מחלקה נפצל את הלולאה כמו קודם, ונסנכרן הפעם את המחלקה
public class Count { private static int count; public static void setCount(int x){count=x;} public static int getCount(){return count;} public static void update(){count++;} } public class ThreadTest { static int count=0; private static class CountRunnable implements Runnable{ public void run(){ for(int j=0;j<10000;j++) synchronized(Count.class){// or c.getClass() for(int i=0;i<10000;i++) Count.update(); } public static void main(String[] args) { CountRunnable ca=new CountRunnable(); Thread t=new Thread(ca); Thread t1=new Thread(ca); Count.setCount(0); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(Count.getCount()); נפצל את הלולאה כמו קודם, ונסנכרן הפעם את המחלקה ולא את האובייקט.

25 בשיעור הבא... נלמד על נושאים מתקדמים ב threads נלמד לתזמן משימות.
נלמד על Java.Util.Concurrent. ועל מבני נתונים חסינים מבעיות ה threads.

26 הטמעה מה ההבדל בין Decorator ל Adapter?
כאשר נוצר thread לאיזה תור הוא נכנס? מה מוציא אותו מהתור? מהו dead lock? תצרו שעון עצר (stopper) שיכול למדוד זמן בתחרות ריצה של 3 מתחרים. כשמגיע קלט enter מהמשתמש, ה thread נעצר והזמן שהוא רץ מוחזר.


Download ppt "Java Threads אליהו חלסצ'י תכנות מתקדם תרגול מספר 6"

Similar presentations


Ads by Google