CS313: Object-Oriented Programming
บทที่ 13: Multithreading
บทที่ 13 Multithreading 13.1 Introduction ในการทํางานของโปรแกรมโดยทั่วไป จะเปนการทํางานตามลําดับของคําสั่งทีละคําสั่ง (sequential processing) แตจะมีการทํางานแบบหนึ่งซึ่งงานตางๆ สามารถทําไดพรอมกัน โดยไมตองรอใหงานนั้นๆ เสร็จ เชน ระบบการหายใจของมนุษย สามารถทํางานพรอมกับระบบการยอยอาหาร หรือสัมผัสทั้ง 5 ของมนุษย อัน ไดแก การมองเห็น การฟง การไดกลิ่น การลิ้มรส และการสัมผัส ในทางคอมพิวเตอรก็มีระบบการทํางานใน ลักษณะนี้เชนกัน ตัวอยางเชน เราสามารถ compile โปรแกรม พรอมกับการสั่งพิมพเอกสาร หรือการเปดเพลง mp3 การทํางานที่พรอมกันหลายๆ งานในเวลาหนึ่งๆ เรียกวา parallel processing หรือ concurrency processing ภาษา Java เปนภาษาที่รองรับการทํางานทั้งแบบ sequential และ แบบ concurrency โดยที่การ เขียนโปรแกรมแบบ concurrency นัน้ จะอยูในลักษณะของ thread thread เปนกระบวนการเขียนโปรแกรมที่สามารถทําใหเกิดงานไดหลายงานในหนึ่งโปรแกรม การ เขียนโปรแกรมแบบ thread ในภาษา Java นี้จะเปน multithreading ซึง่ คุณสมบัตินี้ไมมีในภาษาอื่น เชน C หรือ C++ ซึง่ ถูกจัดเปนภาษาประเภท single-threaded language 13.2 Thread Creating ในการสราง thread สามารถทําได 2 วิธีคือ โดยการสืบทอดจาก class Thread และอีกวิธีหนึ่งคือ การ implement interface ชื่อ Runnable ตัวอยางที่ 1 การสราง thread โดยการสืบทอดจาก class Thread // Creates three threads that run concurrently. public class Simultaneous public static Soda one = Soda two = Soda three
} }
2/2545
{
void main (String[] args) new Soda ("Coke"); new Soda ("Pepsi"); = new Soda ("Mirinda");
{
one.start(); // The start method begins thread processing two.start(); three.start(); // method main
// class Simultaneous
1
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
ตัวอยางที่ 1 (ตอ) // A thread that prints a message 100 times. class Soda extends Thread
{
private String name; Soda (String str) { name = str; } // constructor Soda // The run method defines the concurrent code. public void run() { for (int count = 0; count < 100; count++) System.out.println (name); } // method run }
// class Soda
ตัวอยางที่ 1 เปนตัวอยางการสราง class ชื่อ Soda ซึง่ เปน sub-class ของ class Thread ใน class Thread จะมี method ทีช่ ื่อวา run() ซึง่ จะตองถูก override โดย sub-class ภายใน method run() จะบรรจุ code ทีต่ อ งการใหทํางานเมื่อมีการสั่งให thread นัน้ ๆ ทํางาน โดย method start() ใน class Simultaneous มีการสราง 3 object ชื่อ one, two และ three จาก class Soda ดังนั้นทั้ง 3 object ก็คือ thread นั่นเอง โดย object ทัง้ 3 จะถูกทํางานจากคําสั่ง one.start(); two.start(); และ three.start() ตามลําดับ ในการทํางานจะพบวา ขอความที่ถูกพิมพ จะปะปนกันไป ไมเรียงตามลําดับของการ สัง่ งาน ทั้งนี้เนื่องจากแตละ thread มีอสิ ระในการประมวลผล ตัวอยางที่ 2 การสราง thread โดยการ implement interface ชือ่ Runnable import java.awt.Graphics; import java.awt.Font; import java.util.Date; public class DigitalThreads extends java.applet.Applet implements Runnable { Font theFont = new Font ("TimesRoman", Font.BOLD, 24); Date theDate; Thread runner; public void start() { if (runner == null) { runner = new Thread(this); runner.start(); } } // method start public void stop() { if (runner != null) { runner.stop(); runner = null; } } // method stop
2/2545
2
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
ตัวอยางที่ 2 (ตอ) public void run() { while (true) { theDate = new Date(); repaint(); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } // method run public void paint (Graphics g) { g.setFont (theFont); g.drawString (theDate.toString(), 10, 50); } // method paint } // class DigitalThreads
<TITLE>DigitalThreads Applet <APPLET CODE="DigitalThreads.class" WIDTH=430 HEIGHT=100>
ตัวอยางที่ 2 เปน applet ทีแ่ สดงเวลาปจจุบันของเครื่องคอมพิวเตอร โดยจะมีการสราง object ชื่อ runner จาก class Thread method ทีใ่ ชมีทั้งหมด 4 method ดังนี้ 1. start() เปน method ทีจ่ ะถูกเรียกเมื่อมีการเริ่มประมวลผล applet DigitalThreads 2. stop() เปน method ทีจ่ ะถูกเรียกเมื่อ applet นีจ้ บการทํางาน 3. run() เปน method ทีจ่ ะทํางานเมื่อ thread ที่ชื่อ runner ถูกเรียกโดยคําสั่ง runner.start() 4. paint() เปน method ทีใ่ ชในการแสดงผลทาง graphics 13.3 Thread States: Life Cycle of a Thread วงจรชีวิตของแตละ thread เริ่มตนที่ขั้น born เมือ่ ถูกเริ่มสราง และจะอยูที่ขั้นนี้จนกระทั่ง method start ถูกเรียกประมวลผล ซึ่งจะทําให thread นั้นๆ อยูที่ขั้น ready โดยที่ thread ที่มี priority สูงที่สุดจะถูก ทํางานกอนโดยเขาสูขั้น running แตละ thread จะเขาสูขั้นสุดทายของวงจรชีวิตคือ dead เมื่อ thread นั้น ทํางานใน method run เสร็จเรียบรอย หรือเมื่อถูก terminate ดวยเหตุผลใดๆ ก็ตาม
2/2545
3
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
ในชวงที่ thread หนึ่งๆ อยูในขั้น running thread นัน้ จะสามารถยายไปอยูในขั้น blocked ได เมื่อ มีการเรียกใชงาน input หรือ output ในกรณีนี้ thread ดังกลาวจะกลับมาสูขั้น running เมือ่ กระบวนการ input หรือ output ไดทํางานเสร็จสิ้นลง เมือ่ มีการเรียก method sleep() ใน thread ทีอ่ ยูในขั้น running จะสงผลให thread ดังกลาวเขาสู ขั้น sleeping ซึง่ เหมือนกับการหยุดการทํางานชั่วคราว โดยทั่วไปจะมีการกําหนดเวลาสําหรับการอยูในขั้น sleeping ซึง่ เมือ่ หมดเวลาที่กําหนดไวแลว thread นัน้ ๆ จะกลับสูขั้น running อีกครั้งหนึ่ง เมื่อ thread ในขั้น running มีการเรียก method wait() จะสงผลให thread ดังกลาวเปลี่ยนสถานะ มาอยูในขั้น waiting เพือ่ ทําการรอการตอบสนองจาก object ทีร่ ะบุไวเมื่อมีการเรียก method wait() thread ในขั้น waiting จะกลับเขาสูขั้น running เมือ่ มีการเรียก method notify() หรือ notifyAll() โดย thread อื่นที่เกี่ยว ของกับ object นั้นๆ วงจรชีวิตของ Thread สามารถสรุปไดดังภาพตอไปนี้
born start() I/O completion
sleep interval expires
ready
notify() notifyAll()
Quantum expiration yield interrupt
dispatch (assign a processor)
running issue I/O
wait()
waiting
2/2545
sleep() sleeping
complete
dead
blocked
4
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
13.4 Thread Synchronization ปญหาทีอ่ าจเกิดขึ้นเมื่อมีการทํางานหลาย thread พรอมๆ กัน โดยที่แตละ thread มีการใชขอมูล รวมกัน อาจเกิดขอผิดพลาดกับขอมูลดังกลาวได วิธกี ารแกปญหานี้คือ การใช method synchronized โดยที่ method นีจ้ ะอนุญาตใหเพียง 1 thread เทานั้นที่จะ execute คําสั่งตางๆ ภายใน method synchronized ได สวน thread อืน่ ๆ จะตองรอ (wait) จน กวา thread นัน้ ทํางานใน method synchronized เสร็จกอน ตัวอยางที่ 3 Producer / consumer relationship without Thread Synchronization // Show multiple threads modifying shared object. public class SharedCell { public static void main( String args[] ) { HoldIntegerUnsynchronized h = new HoldIntegerUnsynchronized(); ProduceInteger p = new ProduceInteger( h ); ConsumeInteger c = new ConsumeInteger( h ); p.start(); c.start(); } }
// Definition of threaded class ProduceInteger public class ProduceInteger extends Thread { private HoldIntegerUnsynchronized pHold; public ProduceInteger( HoldIntegerUnsynchronized h ) { super( "ProduceInteger" ); pHold = h; } public void run() { for ( int count = 1; count <= 10; count++ ) { // sleep for a random interval try { Thread.sleep( (int) ( Math.random() * 3000 ) ); } catch( InterruptedException e ) { System.err.println( e.toString() ); } pHold.setSharedInt( count ); }
2/2545
5
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
ตัวอยางที่ 3 (ตอ) System.err.println( getName() + " finished producing values" + "\nTerminating " + getName() ); } } // Definition of threaded class ConsumeInteger public class ConsumeInteger extends Thread { private HoldIntegerUnsynchronized cHold; public ConsumeInteger( HoldIntegerUnsynchronized h ) { super( "ConsumeInteger" ); cHold = h; } public void run() { int val, sum = 0; do { // sleep for a random interval try { Thread.sleep( (int) ( Math.random() * 3000 ) ); } catch( InterruptedException e ) { System.err.println( e.toString() ); } val = cHold.getSharedInt(); sum += val; } while ( val != 10 ); System.err.println( getName() + " retrieved values totaling: " + sum + "\nTerminating " + getName() ); } }
// Definition of class HoldIntegerUnsynchronized public class HoldIntegerUnsynchronized { private int sharedInt = -1; public void setSharedInt( int val ) { System.err.println( Thread.currentThread().getName() + " setting sharedInt to " + val ); sharedInt = val; } public int getSharedInt() { System.err.println( Thread.currentThread().getName() + " retrieving sharedInt value " + sharedInt ); return sharedInt; } }
2/2545
6
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
สังเกตุผลลัพธที่ไดหลังจาก run class SharedCell ตัวอยางที่ 4 Producer / consumer relationship with Thread Synchronization // Show multiple threads modifying shared object. public class SharedCell { public static void main( String args[] ) { HoldIntegerSynchronized h = new HoldIntegerSynchronized(); ProduceInteger p = new ProduceInteger( h ); ConsumeInteger c = new ConsumeInteger( h ); p.start(); c.start(); } }
// Definition of threaded class ProduceInteger public class ProduceInteger extends Thread { private HoldIntegerSynchronized pHold; public ProduceInteger( HoldIntegerSynchronized h ) { super( "ProduceInteger" ); pHold = h; } public void run() { for ( int count = 1; count <= 10; count++ ) { // sleep for a random interval try { Thread.sleep( (int) ( Math.random() * 3000 ) ); } catch( InterruptedException e ) { System.err.println( e.toString() ); } pHold.setSharedInt( count ); } System.err.println( getName() + " finished producing values" + "\nTerminating " + getName() ); } }
2/2545
7
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
ตัวอยางที่ 4 (ตอ) // Definition of threaded class ConsumeInteger public class ConsumeInteger extends Thread { private HoldIntegerSynchronized cHold; public ConsumeInteger( HoldIntegerSynchronized h ) { super( "ConsumeInteger" ); cHold = h; } public void run() { int val, sum = 0; do { // sleep for a random interval try { Thread.sleep( (int) ( Math.random() * 3000 ) ); } catch( InterruptedException e ) { System.err.println( e.toString() ); } val = cHold.getSharedInt(); sum += val; } while ( val != 10 ); System.err.println( getName() + " retrieved values totaling: " + sum + "\nTerminating " + getName() ); } }
// Definition of class HoldIntegerSynchronized that // uses thread synchronization to ensure that both // threads access sharedInt at the proper times. public class HoldIntegerSynchronized { private int sharedInt = -1; private boolean writeable = true; // condition variable public synchronized void setSharedInt( int val ) { while ( !writeable ) { // not the producer's turn try { wait(); } catch ( InterruptedException e ) { e.printStackTrace(); } } System.err.println( Thread.currentThread().getName() + " setting sharedInt to " + val ); sharedInt = val; writeable = false;
2/2545
8
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
ตัวอยางที่ 4 (ตอ) notify(); // tell a waiting thread to become ready } public synchronized int getSharedInt() { while ( writeable ) { // not the consumer's turn try { wait(); } catch ( InterruptedException e ) { e.printStackTrace(); } } writeable = true; notify(); // tell a waiting thread to become ready System.err.println( Thread.currentThread().getName() + " retrieving sharedInt value " + sharedInt ); return sharedInt; } }
เปรียบเทียบผลลัพธที่ไดกับตัวอยางที่ 3 13.5 Controlling Threads จากวงจรชีวิตของ thread ทําใหทราบวา thread ตางๆ สามารถถูกควบคุมไดโดย method ตางๆ ดังนี้ ! suspend() เปนการหยุดการทํางานของ thread ชัว่ คราว ! resume() เปนการเรียก thread ที่ถูก suspend ขึน้ มาทํางานใหม ! sleep ( long milliseconds) เปนการหยุดการทํางานของ thread นานตามเวลาที่กําหนด ตัวอยางที่ 5 bouncing ball import java.awt.event.*; import java.applet.Applet; import java.awt.*; // Simulates a ball on a rubber band. public class Bouncing_Ball extends Applet { private final int SIZE= 300; private Ball ball = new Ball(150, 10, 250, 200); private Graphics page;
2/2545
9
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
ตัวอยางที่ 5 (ตอ) private Control_Panel controls; public void init() { setVisible (true); setSize (SIZE, SIZE); page = getGraphics(); page.setXORMode (getBackground()); } // method init public void start() { controls = new Control_Panel (Thread.currentThread()); controls.start(); ball.pause(); while (ball.moving()) { ball.bounce (page); } } // method start } // class Bouncing_Ball // Various buttons that suspend and resume execution of the applet thread. class Control_Panel extends Thread { private Button suspend = new Button ("suspend"); private Button resume = new Button ("resume"); private Frame frame = new Frame ("Bouncing Ball Control Panel"); private Thread applet_thread; Control_Panel (Thread applet_thread) { this.applet_thread = applet_thread; } // constructor Control_Panel public void run() { Resume_Action resume_action = new Resume_Action (applet_thread); Suspend_Action suspend_action = new Suspend_Action (applet_thread); suspend.addActionListener (suspend_action); resume.addActionListener (resume_action); frame.setLayout (new FlowLayout()); frame.add (suspend); frame.add (resume); frame.pack(); frame.setLocation (250, 200); frame.setVisible (true); } // method run } // class Control_Panel // Action for the suspend button class Suspend_Action implements ActionListener { Thread applet_thread; Suspend_Action (Thread applet_thread) { this.applet_thread = applet_thread; } // Constructor Suspend_Action public void actionPerformed (ActionEvent action) { applet_thread.suspend(); // Suspends the animation } // method actionPerformed } // class Suspend_Action
2/2545
10
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
ตัวอยางที่ 5 (ตอ) // Action for the suspend button class Resume_Action implements ActionListener { Thread applet_thread; Resume_Action (Thread applet_thread) { this.applet_thread = applet_thread; } // constructor Resume_Action public void actionPerformed (ActionEvent action) { applet_thread.resume(); // Resumes the animation } // method actionPerformed } // class Resume_Action // Draws the ball and simulates its movement. class Ball { private final int MOVE = 2; private final float DISTANCE = 0.97f; private final int SIZE = 20; private final int PAUSE = 5; private int x; private int start_y; private int end_y; private int length; private boolean moving_up = true; Ball (int new_x, int new_start_y, int new_end_y, int new_length) { x = new_x; start_y = new_start_y; end_y = new_end_y; length = new_length; } // constructor Ball public void pause() { try { Thread.currentThread().sleep (PAUSE); // Pauses the animation for a short time } catch (InterruptedException exception) { System.out.println ("have an exception"); // ignore the exception and continue } } // method pause void move() { if (moving_up) { end_y = end_y - MOVE; } else { end_y = end_y + MOVE; } } // method move
2/2545
11
CS313: Object-Oriented Programming
บทที่ 13: Multithreading
ตัวอยางที่ 5 (ตอ) void draw_ball (Graphics page) { page.drawOval (x-(SIZE/2), end_y, SIZE, SIZE); page.drawLine (x, start_y, x, end_y); } // draw_ball public boolean moving() { return length != 0; } // method moving public void bounce (Graphics page) { for (int count = 1; count < length; count += MOVE) { draw_ball (page); pause(); draw_ball (page); move(); } moving_up = !moving_up; length = (int) (DISTANCE * length); } // method bounce } // class Ball
<TITLE>Bouncing Ball Applet <APPLET CODE="Bouncing_Ball.class" WIDTH=300 HEIGHT=300>
2/2545
12