200 likes | 218 Views
נושאים מתקדמים ב Threads. תכנות מתקדם 89-210 תרגול מספר 7 תש"ע 2009-2010. אליהו חלסצ'י. בשיעור הקודם למדנו על אופן פעולת ה thrads ב java , ואיך להריץ אותם ברמה בסיסית.
E N D
נושאים מתקדמים ב Threads תכנות מתקדם 89-210תרגול מספר 7 תש"ע 2009-2010 אליהו חלסצ'י
בשיעור הקודם למדנו על אופן פעולת ה thrads ב java, ואיך להריץ אותם ברמה בסיסית. השיטות בהן השתמשנו מתאימות למשימות פשוטות, אך למשימות מורכבות יותר נדרש Higher-Level API, כזה שיסתיר לנו את הלוגיקה מאחורי ניהול ה threads, ויוכל לבצע משימות מתקדמות יותר. הוא נחוץ אף יותר עבור אפליקציות כבדות שמנצלות באופן מלא את המערכות מרובות המעבדים \ ליבות המצויות כיום. בגרסא 5, נכנס java.util.concurrent עליו נלמד היום. הקדמה
תזמון משימות publicclass ThreadTest { privatestaticclass Ping implements Runnable{ publicvoid run(){while(true)System.out.println("ping");} } privatestaticclass Pong implements Runnable{ publicvoid run(){while(true)System.out.println("pong");} } publicstaticvoid main(String[] args) { Ping ping=new Ping(); Pong pong=new Pong(); Thread t=new Thread(ping,"thread 1"); Thread t1=new Thread(pong,"thread 2"); t.start(); t1.start(); } } מחלקה אחת תמיד כותבתping והשנייה תמיד pongאנו רוצים להתחיל מ pingושהשניים יתחלפו בכל חצישנייה. כרגע הם נכתבים לא לפיהסדר ולא לא לפי הקצב
תזמון משימות ניתן לפתור את הבעיה באמצעות sleep למשך שנייה, ולהתחיל את ה Threads בהפרש של חצי שנייה. הפיתרון כמובן מסורבל. publicclass ThreadTest { privatestaticclass Ping implements Runnable{ publicvoid run(){ while(true){ System.out.println("ping"); try {Thread.sleep(1000);} catch (InterruptedException e) {} } } } privatestaticclass Pong implements Runnable{ publicvoid run(){ while(true){ System.out.println("pong"); try {Thread.sleep(1000);} catch (InterruptedException e) {} } } } publicstaticvoid main(String[] args) throws InterruptedException { Ping ping=new Ping(); Pong pong=new Pong(); Thread t=new Thread(ping,"thread 1"); Thread t1=new Thread(pong,"thread 2"); t.start(); long time2,time=System.currentTimeMillis(); while((time2=System.currentTimeMillis())-time<500); t1.start(); } }
תזמון משימות המחלקה Timer יכולה לבצע את כל ההתאמות הדרושות מאחורי הקלעים ולתזמן משימות ב thread שרץ ברקע. ניתן לבטל משימות ע"י קריאה לcancel() אך ה timer ימשיך לרוץ. ניתן לבטל את ה timer עצמו ע"יקריאה ל cancel() שלו. import java.util.Timer; import java.util.TimerTask; publicclass ThreadTest { privatestaticclass Ping extends TimerTask{ publicvoid run(){System.out.println("ping");} } privatestaticclass Pong extends TimerTask{ publicvoid run(){System.out.println("pong");} } publicstaticvoid main(String[] args){ Ping ping=new Ping(); Pong pong=new Pong(); Timer t=new Timer(); t.scheduleAtFixedRate(ping, 0, 1000); t.scheduleAtFixedRate(pong, 500, 1000); } } int i; while((i=System.in.read())!=13); ping.cancel(); בוטל ping ממשיך t pong.cancel(); בוטל pong ממשיך t t.cancel();בוטל t
דוגמת סנכרון משיעור קודם publicclass ThreadTest { privatestaticclass CountAdapter implements Runnable{ Count c; public CountAdapter(Count c){ this.c=c; } publicvoid run(){ for(int i=0;i<100000000;i++) c.update(); } } publicstaticvoid 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); long time=System.currentTimeMillis(); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(c.getCount()); System.out.println((System.currentTimeMillis()-time)/1000); }} publicclass Count { privateintcount; publicvoid setCount(int x){count=x;} publicint getCount(){returncount;} publicsynchronized void update(){count++;} } שיעור קודם שחקנו עם מיקום הסנכרוןוהגענו לפשרה בין מקביליות למהירות. בדוגמא אפשרנו מקביליות על חשבוןמהירות ריצה... זמן הריצה היה כ 46 שניות. כעת נשתמש במשתנה אטומי.
משתנים אטומיים publicclass ThreadTest { privatestaticclass CountAdapter implements Runnable{ Count c; public CountAdapter(Count c){ this.c=c; } publicvoid run(){ for(int i=0;i<100000000;i++) c.update(); } } publicstaticvoid 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); long time=System.currentTimeMillis(); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(c.getCount()); System.out.println((System.currentTimeMillis()-time)/1000); }} import java.util.concurrent.atomic.AtomicInteger; publicclass Count { private AtomicInteger count=new AtomicInteger(0); publicvoid setCount(int x){count.set(x);} publicint getCount(){returncount.get();} publicvoid update(){ count.incrementAndGet();// count++ } } הפעם השתמשנו באובייקטAtomicInteger – רק thread אחד יכול לבצע עליו פעולות. זמן הריצה היה כ 6 שניות.
דוגמא ל deadlock publicclass Friend { private String name; public Friend(String name){this.name = name;} public String getName(){returnthis.name;} publicsynchronizedvoid sayHA(Friend date) { System.out.format("%s says HA to %s%n",this.name, date.getName()); date.sayDA(this); } publicsynchronizedvoid sayDA(Friend date) { System.out.format("%s says DA to %s%n",this.name,date.getName()); } } • נתון הפרוטוקול הבא לפתיחת "דייט": אם אחד המשתתפים אומר "הא" האחר צריך להשיב "דה". • בדוגמא, Alice ו Bob החיים ב threads • נפרדים, יצאו ל"דייט". • שניהם מתפרצים ואומרים "הא". • בגלל הסנכרון על אמירת ה"הא", האובייקטים של Alice ושל Bob נעולים עד שאמירת ה"הא" תסתיים. • אמירת ה"הא" לא מסתיימת עד שהאחר יאמר "דה". • אבל יש גם סנכרון על אמירת ה"דה", ואם האובייקט נעול, ה Thread השני לא יכול להיכנס אליו. • Alice ו Bob תקועים בשתיקה מביכה. publicclass DeadLock { privatestaticclass FriendRun implements Runnable{ private Friend me,date; public FriendRun(Friend m,Friend d){ me=m;date=d;} publicvoid run(){ me.sayHA(date); } } publicstaticvoid main(String[] args) { Friend alice = new Friend("Alice"); Friend bob = new Friend("Bob"); Thread t=new Thread(new FriendRun(alice,bob)); Thread t1=new Thread(new FriendRun(bob,alice)); t.start(); t1.start(); }} Alice says HA to Bob Bob says HA to Alice
t1 FR FR A B B A Start() t2 Start() דוגמא ל deadlock publicclass Friend { private String name; public Friend(String name){this.name = name;} public String getName(){returnthis.name;} publicsynchronizedvoid sayHA(Friend date) { System.out.format("%s says HA to %s%n",this.name, date.getName()); date.sayDA(this); } publicsynchronizedvoid sayDA(Friend date) { System.out.format("%s says DA to %s%n",this.name,date.getName()); } } A B A.sayHA() A נעול publicclass DeadLock { privatestaticclass FriendRun implements Runnable{ private Friend me,date; public FriendRun(Friend m,Friend d){ me=m;date=d;} publicvoid run(){ me.sayHA(date); } } publicstaticvoid main(String[] args) { Friend alice = new Friend("Alice"); Friend bob = new Friend("Bob"); Thread t=new Thread(new FriendRun(alice,bob)); Thread t1=new Thread(new FriendRun(bob,alice)); t.start(); t1.start(); }} B.sayHA() B נעול t1: B.sayDA() א"א B נעול לא יוצאים מ A.sayHa() A נשאר נעול... t2: A.sayDA() א"א A נעול לא יוצאים מ B.sayHa() B נשאר נעול... אף צד לא סיים לומר HA
נעילת אובייקטים ע"י lock import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; publicclass Friend { private String name; private Lock lock = new ReentrantLock(); public Friend(String name){this.name = name;} public String getName(){returnthis.name;} publicboolean iCanLockBoth(Friend date) { Boolean myLock = false; Boolean yourLock = false; try { myLock=lock.tryLock(); yourLock=date.lock.tryLock(); } finally { if (!(myLock&&yourLock)) { if (myLock) lock.unlock(); if (yourLock) date.lock.unlock(); } } return myLock&&yourLock; } publicvoid sayHA(Friend date) { if(iCanLockBoth(date)){ try{ System.out.format("%s says HA to %s%n",this.name,date.getName()); date.sayDA(this); } finally{ lock.unlock(); date.lock.unlock(); } } else System.out.format("%s started to say HA but realized %s already started",name,date.getName()); } iCanLockBoth יחסיר אמת רק אם הצלחנו לנעול את 2המנעולים (שלי ושל ה "דייט") כלומר 2 האובייקטים נעולים ע"י אותו ה thread וכעת אין בעיה שאמירת ה"הא" תצפה ל"דה". Alice says HA to Bob Bob says DA to Alice Bob started to say HA but realized Alice already started
בדוגמאות שראינו עד עתה היה קשר קרוב בין המשימה שצריכה להתבצע לבין ה thread שמריץ אותה. באפליקציות קטנות זה בסדר, אך באפליקציות גדולות הגיוני יותר להפריד את יצירת וניהול ה threads משאר האפליקציה. ממשק ה Executor משמש אותנו לכך. הממשק Executor
לממשק יש מתודה אחת בשם:(public void execute(Runnable r כאשר השליטה איך להריץ את r.run() בידנו. דוגמאות: הרצה ישירה: הרצה ב thread נפרדלכל משימה: הממשק Executor class DirectExecutor implements Executor { publicvoid execute(Runnable r) { r.run(); } } class ThreadPerTaskExecutor implements Executor { publicvoid execute(Runnable r) { new Thread(r).start(); } }
נניח שאנו נרצה לשלוט בכמות ה threads שרצים במקביל, כדי לשלוט בעומס על המערכת. נוכל לממש תור שיאפשר רק ל N מסוים של threads לרוץ מתוכו. שיטה זו נקראת ThreadPool. אופן המימוש: במתודה execute פשוט נכניס את r לתור. נממש מתודה בשם run ששולפת NRunnables מהתור, ומריצה אותם ב threads משלהם. לא נשלוף אחרים כל עוד אלו לא הסתיימו. אבל נצטרך לטפל במקרים שונים של sleep, wait וכו' שזה כבר יותר קשה לממש בצורה טובה. לכן ב java.util.concurrent כבר ממשו ThreadPools חזקים וגמישים. הממשק Executor
ThreadPools import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; publicclass ThreadTest3 { privatestaticvoid delay(long ms){ try { Thread.sleep(ms);} catch (InterruptedException e) {} } privatestaticclass RunnableTask1 implements Runnable{ publicvoid run(){ System.out.println("task1 started"); delay(10000); System.out.println("task1 finished"); } }… publicstaticvoid main(String[] args) { Executor executor = Executors.newSingleThreadExecutor(); executor.execute (new RunnableTask1 ()); executor.execute (new RunnableTask2 ()); executor.execute (new RunnableTask3 ()); ((ExecutorService) executor).shutdown(); } אנו קוראים בצורה סטטית ל factory שיוצר ExectuterService, כאשר במקרה שלנוnewSingleTreadExecutor() מאפשר רקל thread אחד לרוץ בכל פעם. איך יראה הפלט? ובהתאמה המחלקות RunnableTask2 ו RunnableTask 3 task1 started task1 finished task2 started task2 finished task3 started task3 finished
ThreadPools import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; publicclass ThreadTest3 { privatestaticvoid delay(long ms){ try { Thread.sleep(ms);} catch (InterruptedException e) {} } privatestaticclass RunnableTask1 implements Runnable{ publicvoid run(){ System.out.println("task1 started"); delay(10000); System.out.println("task1 finished"); } }… publicstaticvoid main(String[] args) { Executor executor = Executors.newFixedThreadPool (2); executor.execute (new RunnableTask1 ()); executor.execute (new RunnableTask2 ()); executor.execute (new RunnableTask3 ()); ((ExecutorService) executor).shutdown(); } הפעם Executors.newFixedThreadPoolמאפשר רק למספר מסוים של threads לרוץ. איך יראה הפלט? ובהתאמה המחלקות RunnableTask2 ו RunnableTask 3 task1 started task2 started task1 finished task2 finished task3 started task3 finished
ThreadPools import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; publicclass ThreadTest3 { privatestaticvoid delay(long ms){ try { Thread.sleep(ms);} catch (InterruptedException e) {} } privatestaticclass RunnableTask1 implements Runnable{ publicvoid run(){ System.out.println("task1 started"); delay(10000); System.out.println("task1 finished"); } }… publicstaticvoid main(String[] args) { Executor executor = Executors.newCachedThreadPool(); executor.execute (new RunnableTask1 ()); executor.execute (new RunnableTask2 ()); executor.execute (new RunnableTask3 ()); ((ExecutorService) executor).shutdown(); } ל Executors.newCachedThreadPool אין הגבלה על מספר ה threads איך יראה הפלט? ובהתאמה המחלקות RunnableTask2 ו RunnableTask 3 task1 started task2 started task3 started task1 finished task3 finished task2 finished
Callable, Future import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; publicclass ThreadTest3 { privatestaticvoid delay(long ms){ try { Thread.sleep(ms);} catch (InterruptedException e) {} } privatestaticclass CallableTask implements Callable<Integer>{ Integer i; public CallableTask(int i){ this.i=new Integer(i); } public Integer call() throws Exception { System.out.printf("task%d started\n",i); delay(10000); returni; } } publicstaticvoid main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newCachedThreadPool(); Future<Integer> futures[]=new Future[3]; for(int i=0;i<3;i++) futures[i]=executor.submit (new CallableTask(i+1)); executor.shutdown(); for(int i=0;i<3;i++) System.out.printf("task%d finished\n",futures[i].get()); } ExecutorService יודע לעבוד גם עם Callable, שבדומה ל Runnable גם לו מתודה אחת call, אך היא יכולה להחזיר ערך ואף לזרוק exception כרצוננו, וכך קל יותר להעביר מידע. המתודה submit תחזיר את הערך שלcall לתוך אובייקט מסוג Future. ל Future מתודות שימושיות נוספות. task1 started task2 started task3 started task1 finished task3 finished task2 finished
רוב מבני הנתונים של java.util אינם מסונכרנים. ניתן לעטוף אותם בטיפוסים מסונכרנים לדוגמא: כך רק מי שצריך, משלם אתמחיר זמן הריצה... בעוד שפעולות בודדות הן thread-safe, רצף של פעולות התלויות כל אחת בתוצאת קודמתה, אינה thread-safe. טעות נפוצה היא לחשוב ששיטה זו thread-safe לגמריי. בגרסא 5, נוספו מבני הנתונים ב java.util.concurrent. מבני נתונים אלו הם thread-safe לחלוטין, ואף מהירים בהרבה ממבנה רגיל העטוף ב synchronized. מבני נתונים: ArrayBlockingQueue<E> ConcurrentHashMap<K,V> ConcurrentLinkedQueue<E> מבני נתונים מסונכרנים private Map<String,Integer> hm = Collections.synchronizedMap( new HashMap<String,Integer>());
האם תמיד מובטח שהמחשב ימליץ ל Bob לצאת עם Alice? (הקוד בשקף הבא) הראו תוכנית שתבטיח זאת. כתבו תוכנית שמעדכנת HashMap בכמה threads ברמה שחוסר הסנכרון יפגע בתוצאות. עטפו את המחלקה בסנכרון, ומדדו את הזמן. החליפו את מבנה הנתונים ל ConcurrentHashMap ומדדו את הזמן שוב. הטמעה
publicclass ThreadTest4 { static String message; privatestaticclass CorrectorThread extends Thread { publicvoid run() { try {sleep(1000);} catch (InterruptedException e) {} message = "Bob, ask Alice on a date."; } } publicstaticvoid main(String args[]) throws InterruptedException { (new CorrectorThread()).start(); message = "Bob, don't ask Alice on a date."; Thread.sleep(2000); System.out.println(message); } }