Bài giảng Lập trình Java cơ bản - Bài 9: Multithreading - Cao Đức Thông

pdf 51 trang hoanguyen 4130
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình Java cơ bản - Bài 9: Multithreading - Cao Đức Thông", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfbai_giang_lap_trinh_java_co_ban_bai_9_multithreading_cao_duc.pdf

Nội dung text: Bài giảng Lập trình Java cơ bản - Bài 9: Multithreading - Cao Đức Thông

  1. Lập trình Java cơ bản Cao Đức Thông - Trần Minh Tuấn cdthong@ifi.edu.vn, tmtuan@ifi.edu.vn 1
  2. Bài 9. Multithreading • Đa nhiệm và đa tuyến • Tạo lập và sử dụng tuyến • Lớp Thread • Giao tiếp Runnable • Đồng bộ hoá các tuyến • Tuyến ma • Nhóm tuyến • Bài tập 2
  3. Đa nhiệm(multitasking) • Đa nhiệm là kỹ thuật cho phép nhiều công việc đượcthực hiệncùngmột lúc trên máy tính. • Nếu có nhiều CPU, các công việc có thể được thực hiện song song trên từng CPU. Trong trường hợp nhiều công việc cùng chia sẻ một CPU, từng phần của mỗi công việc sẽ được CPU thực hiện xen kẽ. 3
  4. Đa nhiệm(multitasking) Task 1 Task 2 Task 3 Nhiềucông việc thi hành trên mộtCPU 4
  5. Đa nhiệm(multitasking) • Hai kỹ thuật đa nhiệmcơ bản • Đatiến trình (Process-based multitasking): Nhiềuchương trình chạy đồng thời. Mỗi chương trình có một vùng dữ liệu độc lập. • Đatuyến (Thread-based multitasking): Mộtchương trình có nhiều tuyến cùng chạy đồng thời. Các tuyến dùng chung vùng dữ liệu của chương trình. 5
  6. Tuyếnvàđatuyến • Tuyếnlàmạch thi hành độc lập củamộttác vụ trong chương trình. • Mộtchương trình có nhiềutuyếnthựchiện cùng lúc gọilàđatuyến. program program 6
  7. Tạo tuyến • Tuyến trong Java cũng là các đối tượng. • Có hai cách để tạotuyến • Thừakế từ lớp java.lang.Thread • Cài đặtgiaotiếp java.lang.Runnable 7
  8. Cách 1: Kế thừa từ Thread Tạo lớp MyThread kế thừa từ Thread và nạp class MyThread extends Thread { chồng phương thức . run() của lớp Thread. public void run() { } } Tạo và thực thi tuyến. Thread th1 = new MyThread(); Thread th2 = new MyThread(); th1.start(); th2.start(); 8
  9. Cách 1: Kế thừa từ Thread • Khi một tuyến được tạo ra, nó cần gọi start() để đặt tuyến ở trạng thái sẵn sàng. Tiếp theo hệ thống sẽ thực thi các câu lệnh trong run() của tuyến đó. • Tuyến sẽ kết thúc khi làm hết lệnh trong run() hoặc khi stop() được gọi. 9
  10. Tạo tuyến Tạotuyếnmới MyThread th1 = new MyThread(); MyThread th2 = new MyThread(); th1.start(); th2.start(); Sẵn sàng bắt đầu thực thi tuyến 10
  11. Cách 2: Cài đặt Runnable Trong trường hợp lớp đã class MyClass extends SomeClass kế thừa từ một lớp khác, cần cài đặt giao tiếp implements Runnable { Runnable để lớp có thể là . một tuyến. public void run() { Runnable có duy nhất } một phương thức run(). } Tạo và thực thi Thread th1 = new Thread(new MyClass()); tuyến. Thread th2 = new Thread(new MyClass()); th1.start(); th2.start(); 11
  12. Độ ưutiên • Các tuyếntrongJava cóđộ ưutiêntừ Thread.MIN_PRIORITY (giá trị 1) đến Thread.MAX_PRIORITY (giá trị 10) • Tuyếncóđộ ưu tiên càng cao thì càng sớm đượcthựchiện và hoàn thành. • Độ ưutiênmặc định của các tuyến là Thread.NORM_PRIORITY (giá trị 5). • Một tuyếnmới sẽ thừakếđộưutiêntừ tuyến tạoranó. 12
  13. Bộ lậplịch • Bộ lập lịch (scheduler) của Java quản lý các tuyến theo cơ chế phân chia thời gian (timeslicing). Từng tuyến sẽ được cấp một khoảng thời gian ngắn (time quantum) để sử dụng CPU. Trong khi thực thi, nếu đã hết thời gian được cấp thì dù chưakếtthúc tuyếncũng phảitạm dừng để cho các tuyến khác cùng độ ưu tiên dùng CPU. • Các tuyến cùng độ ưu tiên luân phiên sử dụng CPU theo kiểu xoay vòng (round- robin). 13
  14. Bộ lậplịch Priority 10 A B Ví dụ: Tuyến A và B sẽ luân phiên nhau thực thi Priority 9 C cho đến khi kết thúc. Tiếp theo tuyến C sẽ thực thi Priority 8 đến khi kết thúc. Tiếp theo tuyến D, E và F sẽ luân Priority 7 D E F phiên thực thi đến khi kết thúc. Tiếp theo tuyến G Priority 6 G thực thi đến khi kết thúc. Cuối cùng tuyến H và I Priority 5 luân phiên thực thi đến khi Priority 4 kết thúc. Nhận xét: Các tuyến có độ Priority 3 ưu tiên thấp sẽ có nguy cơ Priority 2 H I bị trì hoãn vô hạn định. Priority 1 14
  15. Ví dụ vềđa tuyến • Tạo ra 3 tuyếnvới độ ưutiênmặc định. Công việc của mỗi tuyến là ngủ trong một thời gian ngẫu nhiên từ 0 đến 5 giây. Sau khi ngủ xong, các tuyến sẽ thông báo ra màn hình. 15
  16. Ví dụ vềđa tuyến class PrintThread extends Thread { private int sleepTime; public PrintThread( String name ) { super( name ); sleepTime = ( int ) ( Math.random() * 5000); System.out.println( getName() + " have sleep time: " + sleepTime); } 16
  17. Ví dụ vềđa tuyến // method run is the code to be executed by new thread public void run() { try { System.out.println( getName() + " starts to sleep"); Thread.sleep( sleepTime ); } // sleep() may throw an InterruptedException catch ( InterruptedException e) { e.printStackTrace(); } System.out.println( getName() + " done sleeping" ); } } 17
  18. Ví dụ vềđa tuyến public class ThreadTest { public static void main( String [ ] args ) { PrintThread thread1 = new PrintThread( "thread1" ); PrintThread thread2 = new PrintThread( "thread2" ); PrintThread thread3 = new PrintThread( "thread3" ); System.out.println( "Starting threads" ); thread1.start(); // start and ready to run thread2.start(); // start and ready to run thread3.start(); // start and ready to run System.out.println( "Threads started, main ends\n" ); } } 18
  19. Ví dụ vềđa tuyến thread1 have sleep time: 622 thread2 have sleep time: 4543 thread3 have sleep time: 1622 Starting threads Threads started, main ends thread1 starts to sleep thread2 starts to sleep thread3 starts to sleep thread1 done sleeping thread3 done sleeping thread2 done sleeping 19
  20. Một số phương thức của Thread • void sleep(long millis); // ngủ • void yield(); // nhường điều khiển • void interrupt(); // ngắt tuyến • void join(); // yêu cầu chờ kết thúc • void suspend(); // deprecated • void resume(); // deprecated • void stop(); // deprecated 20
  21. Vòng đờicủa tuyến born start read y quantum I / expiration dispatch O (a ssig n a c l yield o l m y A inte rrup t processor) f y p i l f e t i running t i o t o n o n n iss ue r c I o o / O t m r ai p e q w e p ue e le st l t s e wa iting sleeping dead blocked slee p inte rval expires 21
  22. Đồng bộ hoá tuyến • Việc các tuyến trong chương trình cùng truy nhập vào một đối tượng có thể sẽ đem lại kết quả không như mong muốn. Ví dụ: Tuyến A cập nhật đối tượng X và tuyến B đọc dữ liệu từ X. Rất có thể xảy ra sự cố là tuyến B đọc dữ liệu chưa được cập nhật. • Đồng bộ hoá tuyến (thread synchronization) giúp cho tạimỗithời điểmchỉ có mộttuyến có thể truy nhậpvàođốitượng còn các tuyến khác phải đợi. Ví dụ: Trong khi tuyến A cập nhật X thì tuyến B chưa được đọc. 22
  23. Đồng bộ hoá tuyến • Dùng từ khoá synchronized trên các phương thức để thực hiện đồng bộ hoá. • Đối tượng khai báo phương thức synchronized sẽ có một bộ giám sát (monitor). Bộ giám sát đảm bảo tại mỗi thời điểm chỉ có một tuyến được gọi phương thức synchronized. • Khi một tuyến gọi phương thức synchronized, đối tượng sẽ bị khoá. Khi tuyến đó thực hiện xong phương thức, đối tượng sẽ được mở khoá. 23
  24. Đồng bộ hoá Thread • Trong khi thực thi phương thức synchronized, một tuyến có thể gọi wait() để chuyển sang trạng thái chờ cho đến khi một điều kiện nào đó xảy ra. Khi tuyến đang chờ, đối tượng sẽ không bị khoá. • Khi thực hiện xong công việc trên đối tượng, một tuyến cũng có thể thông báo (notify) cho các tuyến khác đang chờ để truy nhập đối tượng. • Deadlock: Tuyến A chờ tuyến B và tuyến B cũng chờ tuyến A. 24
  25. Quan hệ Producer-Consumer • Giả sử có 2 tuyến: Producer ghi dữ liệu vào một buffer và Consumer đọc dữ liệu từ buffer => Cần có sự đồng bộ hoá nếu không dữ liệu có thể bị Producer ghi đè trước khi Consumer đọc được hoặc Consumer có thể đọc một dữ liệu nhiều lần khi Producer chưa sản xuất kịp. đọcghi Consumer buffer Producer 25
  26. Quan hệ Producer-Consumer • Giải pháp đồng bộ hoá: • Trước khi tiếp tục sinh dữ liệu và đưa vào buffer, Producer phải chờ (wait) Consumer đọc xong dữ liệu từ buffer. • Khi Consumer đọc xong dữ liệu, nó sẽ thông báo (notify) cho Producer biết để tiếp tục sinh dữ liệu. • Nếu Consumer thấy trong buffer không có dữ liệu hoặc dữ liệu đó đã được đọc rồi, nó sẽ chờ (wait) cho tới khi nhận được thông báo có dữ liệu mới. • Khi Producer sản xuất xong dữ liệu, nó thông báo (notify) cho Consumer biết. 26
  27. Ví dụ về P-C: Không đồng bộ class Buffer { private int buffer = -1; public void set( int value ) { buffer = value; } public int get() { return buffer; } } 27
  28. Ví dụ về P-C: Không đồng bộ class Producer extends Thread { private Buffer sharedBuffer; public Producer( Buffer shared ) { super( "Producer" ); sharedBuffer = shared; } 28
  29. Ví dụ về P-C: Không đồng bộ public void run() { for ( int count = 1; count <= 5; count++ ) { try { Thread.sleep( ( int ) ( Math.random() * 3000 ) ); sharedBuffer.set( count ); System.out.println( "Producer writes " + count); } catch ( InterruptedException e ) { e.printStackTrace(); } } System.out.println( getName() + " finished."); } } 29
  30. Ví dụ về P-C: Không đồng bộ class Consumer extends Thread { private Buffer sharedBuffer; public Consumer( Buffer shared ) { super( "Consumer" ); sharedBuffer = shared; } 30
  31. Ví dụ về P-C: Không đồng bộ public void run() { for ( int count = 1; count <= 5; count++ ) { try { Thread.sleep( ( int ) ( Math.random() * 3000 ) ); System.out.println( "Consumer reads " + sharedBuffer.get()); } catch ( InterruptedException e ) { e.printStackTrace(); } } System.out.println( getName() + " finished."); } } 31
  32. Ví dụ về P-C: Không đồng bộ public class SharedBufferTest1 { public static void main( String [] args ) { // create shared object used by threads Buffer sharedBuffer = new Buffer(); // create producer and consumer objects Producer producer = new Producer( sharedBuffer ); Consumer consumer = new Consumer( sharedBuffer ); producer.start(); // start producer thread consumer.start(); // start consumer thread } } 32
  33. Kếtquả khi không đồng bộ Producer writes 1 Producer writes 2 Consumer reads 2 Producer writes 3 Producer writes 4 Consumer reads 4 Producer writes 5 Producer finished. Consumer reads 5 Consumer reads 5 Consumer reads 5 Consumer finished. 33
  34. Ví dụ về P-C: Có đồng bộ class Buffer // Thiết kế lại lớp Buffer { private int buffer = -1; private boolean writable = true; public synchronized void set( int value ) { while ( ! writable ) { try { wait(); } catch ( InterruptedException e ) { e.printStackTrace(); } } buffer = value; writable = false; notify(); 34 }
  35. Ví dụ về P-C: Có đồng bộ public synchronized int get() { while ( writable ) { try { wait(); } catch ( InterruptedException e ) { e.printStackTrace(); } } writable = true; notify(); return buffer; } } 35
  36. Kếtquả khi có đồng bộ Producer writes 1 Consumer reads 1 Producer writes 2 Consumer reads 2 Producer writes 3 Consumer reads 3 Producer writes 4 Consumer reads 4 Producer writes 5 Producer finished. Consumer reads 5 Consumer finished. 36
  37. Tạotuyếntừ giao tiếp Runnable • Một lớp có thể trở thành một tuyến khi cài đặt giao tiếp Runnable (giao tiếp này chỉ có một phương thức run() duy nhất). • Ví dụ: Tạo applet có quả bóng chạy 37
  38. Tạotuyếntừ giao tiếp Runnable import java.awt.*; import java.applet.*; public class BallFlying extends Applet implements Runnable { Thread animThread = null; int ballX = 0, ballY =50; int dx=1, dy=2; boolean stopRun = false; public void start() { // applet starts if (animThread == null) { animThread = new Thread(this); animThread.start(); } } 38
  39. Tạotuyếntừ giao tiếp Runnable public void stop() { // applet stops stopRun = true; } public void run() { this.setBackground(Color.CYAN); while (! stopRun) { moveBall(); delay(5); } } private void delay(int miliSeconds) { try { Thread.sleep(miliSeconds); } catch (Exception e) { System.out.println("Sleep error !"); } } 39
  40. Tạotuyếntừ giao tiếp Runnable private void moveBall() { ballX+=dx; ballY+=dy; if (ballY > getSize().height - 30) dy=-dy; if (ballX > getSize().width - 30) dx=-dx; if (ballY < 0) dy=-dy; if (ballX < 0) dx=-dx; repaint(); } public void paint(Graphics g) { g.fillOval(ballX,ballY, 30, 30); } } 40
  41. Kếtquả thựcthi 41
  42. Tuyến ma (daemon thread) • Tuyến ma thường là tuyến hỗ trợ môi trường thực thi của các tuyến khác. Ví dụ: garbage collector của Java là một tuyến ma. • Chương trình kết thúc khi tất cả các tuyến không phải tuyến ma kết thúc. • Các phương thức với tuyến ma: • void setDaemon(boolean isDaemon); // đặt tuyến trở thành tuyến ma • boolean isDaemon(); // kiểm tra tuyến có phải tuyến ma không 42
  43. Nhóm tuyến (thread group) • Các tuyến có thể được đưa vào trong cùng một nhóm thông qua lớp ThreadGroup. Ví dụ: nhóm tuyến tìm kiếm dữ liệu trên các tập dữ liệu khác nhau. • Một nhóm tuyến chỉ có thể xử lý trên các tuyến trong nhóm, ví dụ: ngắt tất cả các tuyến. • Có thể tạo ra các nhóm tuyến là nhóm con của một nhóm tuyến khác. • Nhóm tuyến đặc biệt: system, main 43
  44. Lớp Timer • Hai lớp liên quan tới xử lý công việc theo thời gian • javax.swing.Timer • java.util.Timer • Lớp java.swing.Timer • Đơn giản, dễ dùng trên GUI • Lớp java.util.Timer • Nhiều tính năng hơn java.swing.Timer 44
  45. Ví dụ: Đếm ngược import java.awt.*; import java.awt.event.*; import java.applet.Applet; public class CountDown extends Applet implements ActionListener { private TextField timeField; private Button startButton; private Button stopButton; private javax.swing.Timer timer; private int count; public void init() { timeField = new TextField(6); timeField.setFont(new Font("sansserif", Font.PLAIN, 18)); startButton = new Button("Start"); stopButton = new Button("Stop"); 45
  46. Ví dụ: Đếm ngược add(timeField); add(startButton); add(stopButton); startButton.addActionListener(this); stopButton.addActionListener(this); timer = new javax.swing.Timer(10, this); count = 0; } // end init() 46
  47. Ví dụ: Đếm ngược public void actionPerformed(ActionEvent e) { if ( e.getSource() == startButton) timer.start(); else if (e.getSource() == stopButton) timer.stop(); else { count++; int hsecond = count%100; int totalSecond = (count/100); int h = totalSecond/3600; int secondLeft = totalSecond%3600; int m = secondLeft/60; int s = secondLeft%60; timeField.setText("“ + h + ":" + m + ":" + s + ":" + hsecond); } } } 47
  48. Bài tập 1. Sử dụng kĩ thuật khung hình phụ để cải tiến chương trình vẽ bóng. 2. Mở rộng chương trình vẽ bóng để cho phép hai quả bóng cùng chạy trên màn hình với tốc độ khác nhau (Xem lạibàivề Graphics). 3. Mở rộng chương trình vẽ bóng để cho phép trong khi bóng chạy, nếungười dùng click chuột vào mộtvítrínàođó trên màn hình thì bóng sẽđược chuyểnra vị trí đó. 48
  49. Bài tập 4. Mộtkỹ thuậthoạt hình khác là xem mỗi đối tượng chuyển động như mộttuyến độclập, ví dụ: quả bóng. Tuyến chính củachương trình sẽ liên tụclấydữ liệutừ quả bóng để thể hiện ra màn hình (paint() và repaint()) trong khi tuyếnquả bóng sẽ thực hiện chuyển động thông qua việcthay đổigiátrị toạđộcủanó. Viếtchương trình xây dựng lớp Ball kế thừa từ lớpThread vàtừ đó tạocácquả bóng di chuyểntrên mànhình. 49
  50. Bài tập 5. Viết chương trình tạo 2 tuyến: một tuyến tìm kiếm các số nguyên tố từ 1000 đến 1000000 và một tuyến tính tổng giá trị của các số nguyên tố tìm được. Chú ý đồng bộ tuyến. 6. Viếtchương trình tạomảng có 1000000 phầntử, sau đótạo2 tuyến để sắpxếp2 nửamảng, cuối cùng ghép 2 mảng đã sắp xếp. So sánh cách làm trên với cách sắp xếp trực tiếp toàn bộ mảng. 50
  51. Bài tập 7. Một vấn đề với Producer-Consumer là nếu việc tiêu thụ chậm hơn việc sản xuất thì Producer sẽ phải đợi Consumer. Hãy thiết kế lại ví dụ đã học để cho phép buffer có thể lưu cùng lúc 5 giá trị (dùng mảng), và Producer có thể ghi vào buffer này cho đến khi đầy mới phải đợi. 8. Mở rộng chương trình trên với buffer là một hàng đợi (FIFO). 51