intTypePromotion=1
zunia.vn Tuyển sinh 2024 dành cho Gen-Z zunia.vn zunia.vn
ADSENSE

Giáo trình java cơ bản - Chương 8

Chia sẻ: Tong Van Toan | Ngày: | Loại File: DOC | Số trang:27

221
lượt xem
91
download
 
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Luồng là một thuộc tính duy nhất của Java. Nó là đơn vị nhỏ nhất của đoạn mã có thể thi hành được để thực hiện một công việc nhất định. Ngôn ngữ Java, máy ảo Java cả hai đều là các hệ thống phân luồng.

Chủ đề:
Lưu

Nội dung Text: Giáo trình java cơ bản - Chương 8

  1. Chương 8 ĐA LUỒNG (Multithreading) Mục tiêu: Sau khi kết thúc chương này, bạn có thể:  Định nghĩa một luồng (thread)  Mô tả đa luồng  Tạo và quản lý luồng  Hiểu được vòng đời của luồng  Mô tả một luồng daemon  Hiểu việc thiết lập mức ưu tiên luồng  Giải thích được cần thiết của sự đồng bộ  Hiểu được cách áp dụng vào các từ khoá đồng bộ như thế nào   Liệt kê những nhược điểm của sự đồng bộ  Giải thích vai trò của các phương thức wait(), notify()  và notifyAll().  Mô tả một điều kiện khoá chết (deadlock) 8.1 Giới thiệu Luồng là một thuộc tính duy nhất của Java. Nó là đơn vị nhỏ nhất của đoạn mã có thể thi   hành  được  để  thực hiện một công việc nhất  định. Ngôn ngữ  Java, máy  ảo Java cả  hai   đều là các hệ thống  phân luồng. 8.2 Đa luồng Java hổ trợ đa luồng, nó có khả năng làm việc với nhiều luồng. Một ứng dụng có   thể bao hàm nhiều luồng. Mỗi luồng được gán một công việc cụ thể, chúng được thực thi   đồng thời với các luồng khác. Đa luồng làm giảm tối đa thời gian nhàn rỗi của hệ thống. Điều này cho phép bạn  viết các chương trình có hiệu quả cao với sự tận dụng tối đa CPU. Mỗi phần của chương  trình  được gọi một luồng, mỗi luồng  định nghĩa một cấch thực hiện.  Đây là  một trường   hợp đặc biệt của đa nhiệm. Trong đa nhiệm, nhiều chương chương trình chạy đồng thời, mỗi chương trình có  ít nhất một luồng trong nó. Một bộ  vi xử  lý  thực thi tất cả  các chương trình này. Qua  đó   chương trình  được thực thi như  là  đồng thời, trên thực tế bộ vi xử lý chuyển qua chuyển   lại giữa các chương trình. 8.3 Tạo và quản lý luồng Khi các chương trình Java được thực thi, luồng chính đã đang được thực hiện. Hai  Đa luồng (Multithreading) 189
  2. yếu tố quan trong luồng chính (main) là:  Các luồng con sẽ được tạo ra từ nó.  Nó  là  luồng cuối cùng kết thúc việc thực hiện. Ngay khi luồng chính   ngừng thực thi, chương trình bị chấm dứt. Cho dù luồng chính được tạo ra một cách tự động với chương trình thực thi, nó có   thể được điều khiển thông qua một đối tượng luồng. Các luồng có thể được tạo ra từ hai cách:  Khai báo lớp là  lớp con của lớp Thread, và  phương thức run() của lớp Thread  cần được định nghĩa đè. Ví dụ: class Mydemo extends Thread { public void run() { //thực thi } }  Khai báo lớp cài đặt giao diện Runnable. Rồi thì định nghĩa phương thức run(). class Mydemo implements Runnable { public void run() { //thực thi } } Chương trình 8.1 sẽ chỉ ra sự điều khiển luồng chính như thế nào Chương trình 8.1 import java.io.*;  public class Mythread extends Thread { public static void main(String args[]) { Thread t = Thread.currentThread(); System.out.println("The current Thread is :" + t); t.setName("MyJavaThread"); System.out.println("The thread is now named: " + t); try{ for(int i = 0; i 
  3. Thread.sleep(1500); } } catch(InterruptedException e) { System.out.println("Main thread interupted"); } } } Hình sau đây sẽ chỉ ra kết quả xuất ra màn hình của chương trình trên Hình 8.1 Luồng Trong kết quả xuất ra ở trên [main, 5 , main] [main, 5 , main] Nhóm luồng mà nó phụ thuộc vào Quyền ưu tiên được đặt bởi JVM Tên của luồng Mỗi luồng trong chương trình Java  được cấp một quyền  ưu tiên. Máy  ảo Java   không bao giờ thay đổi quyền  ưu tiên của luồng. Quyền ưu tiên vẫn còn là hằng số cho   đến khi luồng bị ngắt. Mỗi   luồng   có   một   giá   trị   ưu   tiên   nằm   trong   khoảng   của   một  Đa luồng (Multithreading) 191
  4. Thread.MIN_PRIORITY   (=1),   và   đến  Thread.MAX_PRIORITY   (=10).   Mỗi  luồng   thuộc  vào một nhóm luồng, và  mỗi nhóm luồng có  quyền  ưu tiên của chính nó. Mỗi luồng có   một giá trị ưu tiên mặc định Thread.NORM_PRIORITY là 5. Luồng mới thừa kế quyền ưu  tiên của luồng mà tạo ra nó. Lớp Thread có vài phương thức khởi tạo, hai trong số các phương thức khởi dựng  được đề cập đến dưới đây:  public Thread(String threadname) Cấu trúc một luồng với tên là “threadname”  public Thread() Cấu trúc một luồng với tên “Thread, gép với một số; lví  dụ, Thread­1, Thread­2,  v.v… Chương   trình   bắt   đầu   thực   thi   luồng   với   việc   gọi   phương   thức   start()   của   lớp   Thread. Phương thức này sẽ  gọi phương thức run(), nơi  định nghĩa công việc  được thực  thi. Phương thức này có thể được định nghĩa đè trong lớp con của lớp Thread, hoặc trong  đối tượng cài đặt giao diện Runnable. 8.4 Vòng đời của Luồng Hình 8.3 Vòng đời của luồng 8.5  Trạng thái của  luồng và các phương thức của lớp Thread Một luồng vừa  được tạo ra có  trạng thái 'born'. Luồng không bắt  đầu chạy ngay   lập tức sau khi nó   được tạo ra. Nó   đợi phương thức start() của chính nó  được gọi. từ   đó   nó ở trạng thái “sẵn sàng để chạy”. Luồng đi vào trạng thái “đang chay” khi hệ thống cấp  quyền sử dụng bộ vi xử lý cho nó. Bạn có thể sử dụng phương  thức sleep() để tạm thời dừng sự thực thi của luồng.   Luồng trở thành sẵn sàng sau khi phương thức sleep() kết thúc. Luồng khi roi vào trạng   thái 'ngủ' (sleeping) không sử dụng bộ vi xử lý. Luồng đi vào trạng thái “đợi" (waiting) khi   192 Core Java
  5. một luồng gọi phương thức wait().  Khi các luồng khác sử dụng xong đối tượng, gọi phương thức notify(), luồng trở lại  trạng thái “sẵn sàng" (ready). Luồng đi vào trạng thái “blocked”  khi nó đang thực thi các   phép toán vào/ra (Input/output). Nó   đi vào trạng thái "sẵn sàng" khi các phương thức   vào/ra  nó   đang  đợi   được   hoàn  thành.   Luồng   đi   vào  trạng  thái   “chết”   (dead)   sau  khi  phương thức run() đã được thực thi xong, hoặc khi phương thức stop() của nó được gọi. Bên   cạnh   các   phương   thức   đã   được   đề   cập     ở   trên,   lớp   Thread   còn   có   các   phương thức sau: Phương thức Mục đích enumerate(Thread t) Sao chép tất cả  các luồng hiện hành vào mảng  được chỉ  định từ nhóm luồng, và các nhóm con của nó. getName() Trả về tên của luồng isAlive() Trả về True, nếu luồng là vẫn còn tồn tại (sống) getPriority() Trả về mức ưu tiên của luồng setName(String name) Đặt tên cho luồng.  join() Đợi cho đến khi luồng chết. isDaemon() Kiểm   tra   nếu   luồng   là   luồng   một   luồng   chạy   ngầm  (deamon) setDeamon(boolean on) Đánh dấu luồng như là luồng chạy ngầm hoặc  resume() Tiếp tục thực hiện từ trạng thái treo sleep() Dừng thực thi luồng một khoáng thời gian. start() Gọi phương thức run() để bắt đầu một luồng. Bảng 8.1 Các phương thức của một lớp luồng Điều phối round­robin đề cập đến các luồng với cùng mức ưu tiên. Khi các luồng   cùng mức ưu tiên thực thi cùng một thời điểm thì phương pháp chia sẻ thời gian sử dụng  bộ vi xử lý  round­robin tự động được áp dụng. Phiên bản mới nhất của Java không hổ  trợ  các phương thức Thread.suspend()  (treo), Thread.resume() (trở lại) và Thread.stop() (dừng), vì các phương thức tạo ra nguy  cơ khoá chết (deadlock), trong khi phương thức stop() không an toàn. 8.6 Thời gian biểu luồng Hầu hết các chương trình Java là đa luồng. Nhưng CPU chỉ có khả năng thực thi  một luồng tại một thời  điểm. Khi có  nhiều hơn một luồng có  cùng mức  ưu tiên thực thi   trong cùng một thời điểm thì người lập trình, hoặc máy ảo Java, hoặc hệ điều hành đảm   bảo rằng CPU được chia sẻ giữa chúng. Điều này được gọi là điều khiển luồng (thời gian  biểu luồng). Đa luồng (Multithreading) 193
  6. Không có  máy  ảo Java nào có  cơ  chế  cụ  thể  cho việc  điều khiển luồng. Một số  môi trường Java hổ trợ việc chia nhỏ thời gian. Ở đây, mỗi luồng nhận một phần nhỏ của  thời gian bộ vi xử lý, được gọi là  định lượng (quantum). Luồng có thể thực thi tác vụ của   chính nó  trong suốt khoảng thời gian  được cấp  đấy. Sau khoảng thời gian này, luồng   phải tạm dừng thực hiện, ngay cả khi nó chưa hoàn thành. Luồng kế tiếp có cùng quyền   ưu tiên sẽ sử dụng bộ vi xử lý trong khoảng thời tiép theo. Java điều khiển việc chia nhỏ   thời gian giữa tất cả các luồng có cùng mức ưu tiên.  Phương thức setPriority() có  một tham số  kiểu số  nguyên dùng  để   đặt mức  ưu   tiên của luồng. Đây là giá trị nằm trong khoảng 1 đến 10, mặc khác, phương thức có thể   gây ra ngoại lệ IllegalArgumentException. Phương thức yield() tam dừng luồng và tạo khả năng cho các luồng khác một một  cơ  hội  được thực thi. Phương thức này thích hợp cho các hệ  thống không chia sẻ  thời   gian (non­time­sliced), nơi mà  các luồng hiện thời hoàn thành việc thực hiện trước khi   các luồng có  quyền  ưu tiên ngang nhau kế  tiếp tiếp  được thực thi.  Ở   đây, bạn sẽ  gọi   phương thức yield() tại những khoảng thời gian riêng biệt  để  có  thể  tất cả  các luồng có   quyền ưu tiên ngang nhau chia sẻ thời gian thực thi CPU. Chương trình 8.2 thể hiện quyền ưu tiên của luồng: Chương trình 8.2 class PriorityDemo { Priority t1,t2,t3; public PriorityDemo(){ t1 = new Priority(); t1.start(); t2 = new Priority(); t2.start(); t3 = new Priority(); t3.start(); } public static void main(String args[]){ new PriorityDemo(); } class Priority extends Thread implements Runnable{ int sleep;   int prio = 3; public Priority(){ sleep += 100; prio++; setPriority(prio); } 194 Core Java
  7. public void run(){ try{ Thread.sleep(sleep); System.out.println("Name "+ getName()+" Priority =   "+ getPriority()); }catch(InterruptedException e){ System.out.println(e.getMessage()); } } } } Kết quả hiển thị như hình 8.4    Hình 8.4 Quyền ưu tiên luồng 8.7 Luồng chạy ngầm (deamon) Một chương trình Java bị ngắt chỉ sau khi tất cả các luồng thực thi xong. Trong  Java có hai loại luồng:  Luồng người sử dụng  Luồng chạy ngầm (deamon) Người sử  dụng tạo ra các luồng người sử  dụng, trong khi các luồng deamon là  các luồng nền. Luồng deamon cung cấp các dịch vụ  cho các luồng khác. Máy  ảo Java   thực hiện tiến trình thoát, khi đó chỉ còn duy nhất  luồng deamon vẫn còn sống. Máy ảo  Đa luồng (Multithreading) 195
  8. Java có   ít nhất một luồng deamon là  luồng “garbage collection” (thu lượm tài nguyên ­   dọn rác). Luồng dọn rác thực thi chỉ  khi hệ thồng không có tác vụ nào. Nó là một luồng   có quyền ưu tiên thấp. Lớp luồng có hai phương thức để làm việc với luồng deamon:  public void setDaemon(boolean on)  public boolean isDaemon() 8.8 Đa luồng với Applets Trong khi đa luồng là  rất hữu dụng trong các chương trình  ứng dụng độc lập, nó   cũng đáng được quan tâm với các ứng dụng trên Web. Đa luồng được sử dụng trên web,   ví dụ, trong các trò chơi đa phương tiện, các bức ảnh đầy sinh khí, hiển thị các dòng chữ   chạy qua lại trên biểu ngữ, hiển thị   đồng hồ  thời gian như  là  một phần của trang Web   v.vv… Các chức năng này tạo cho các trang web quyến rũ và bắt mắt. Chương trình Java dựa trên Applet thường sử  dụng nhiều hơn một luồng. Trong  đa luồng với Applet, lớp java.applet.Applet  được thừa kế   để  tạo ra các applet. Vì  Java   không hổ trợ  đa thừa kê từ lớp, vì  thế  nó không thể thừa kế trực tiếp từ lớp Thread. Tuy  nhiên, chúng ta sử dụng một đối tượng người sử dụng đã định nghĩa, mà đối tượng  này đã được dẫn xuất từ lớp Thread. Hoặc cài đặt giao diện  Runnable  Chương trình 8.3 chỉ ra điều này thực hiện như thế nào: Chương trình 8.3 import java.awt.*; import java.applet.*; public class Myapplet extends Applet implements Runnable { int i; Thread t; /**  * Myapplet constructor comment.  */ public void init(){ t = new Thread(this); t.start(); } public void paint(Graphics g){ g.drawString(" i = "+i,30,30); } public void run(){ for(i = 1;i
  9. Thread.sleep(500); }catch(InterruptedException e){ System.out.println(e.getMessage()); } } } } Trong chương trình này, chúng ta tạo ra một Applet tên là  Myapplet, và  cài  đặt   giao diện Runnable để cung cấp khả năng đa luồng cho applet. Sau đó, chúng ta tạo ra   một thể hiện (instance) lớp Thread, với thể hiện applet hiện thời như là một tham số. Sau   đó gọi phương thức start() để thực thi luồng. Phương thức run() sẽ được gọi và đây chính   là phần công việc của luồng. Chúng ta in số từ 1 đến 20 với thời gian kéo trễ là 500 mili  giây giữa mỗi số. Phương thức sleep()  được gọi  để  tạo thời gian trễ  này.  Đây là    một   phương thức tĩnh  được  định nghĩa trong lớp Thread. Nó  cho phép luồng dừng   (ngủ)  trong khoản thời gian đó. Kết quả chạy chương trình như sau: Hình 8.5 Đa luồng với Applet 8.9 Nhóm luồng  Một lớp nhóm luồng (ThreadGroup) quản  lý  một  nhóm các luồng.  Ví  dụ, một  nhóm luồng trong một trình duyệt có thể quản lý tất cả các luồng của một applet. Tất cả  các luồng trong máy ảo Java phụ thuộc vào các nhóm luồng mặc định. Mỗi nhóm luồng  có  một nhóm luồng cha. Vì  thế, các nhóm hình thành một cấu trúc dạng cây. Nhóm  luồng “hệ thống” là gốc của tất cả các nhóm luồng. Một nhóm luồng có thể là thành phần  của cả các luồng, và các nhóm luồng. Đa luồng (Multithreading) 197
  10. Lớp  ThreadGroup có hai phương thức  thiết lập là:  public ThreadGroup(String str) Ở đây, “str” là tên của nhóm luồng mới nhất được tạo ra.  public ThreadGroup(ThreadGroup tgroup, String str) Ở đây, “tgroup” chỉ ra luồng đang chạy hiện thời như là luồng cha, “str” là tên của  nhóm luồng đang được tạo ra. Một số các phương thức trong nhóm luồng (ThreadGroup):  public synchronized int activeCount() Trả về số lượng các luồng đang hoạt động trong nhóm luồng  public sunchronized int activeGroupCount() Trả về số lượng các nhóm  đang hoạt động trong nhóm luồng  public final String getName()  Trả về tên của nhóm luồng  public final ThreadGroup getParent() Trả về cha của nhóm luồng 8.10 Sự đồng bộ luồng Trong khi đang làm việc với nhiều luồng, nhiều hơn một luồng có thể muốn thâm  nhập cùng một biến tại cùng thời  điểm. Ví  dụ, một luồng có  thể  cố  gắng  đọc dữ  liệu,  trong khi luồng khác cố  gắng thay  đổi dữ  liệu. Trong trường hợp này, dữ  liệu có  thể  bị  sai. Trong những trường hợp này, bạn cần cho phép một luồng hoàn thành trọn vẹn  tác vụ của nó, và rồi thì mới cho phép các luồng kế tiếp thực thi. Khi hai hoặc nhiều hơn  một luồng cần thâm nhập đến một tài nguyên được chia sẻ, bạn cần chắc chắn rằng tài   nguyên đó sẽ được sử dụng chỉ bởi một luồng tại một thời điểm. Tiến trình này được gọi  là “sự  đồng bộ”, (synchronization)  được sử dụng  để giải quyết vấn  đề này, Java là ngôn  ngữ  duy nhất hổ trợ sự đồng bộ ở mức ngôn ngữ. Phương thức “đồng bộ” (synchronized)   báo cho hệ thống đặt khóa trên tài nguyên. Mấu chốt của sự   đồng bộ  hóa là  khái niệm “monitor” (giám sát), hay còn gọi  “semaphore” (cờ  hiệu). Một “monitor” là  một  đối tượng mà   được   khóa  độc quyền. Chỉ   một luồng có thể có monitor tại mỗi thời điểm. Tất cả các luồng khác cố gắng thâm nhập  vào monitor sẽ bị trì hoãn, cho đến khi luồng đầu tiên thoát khỏi monitor. Các luồng khác  được báo chờ đợi monitor. Một luồng có thể monitor một đối tượng nhiều lần. 1. Đồng bộ mã Tất cả  các  đối tượng trong Java  được liên kết với các monitor của riêng nó.  Để  đăng nhập vào monitor của một  đối tượng, ta sử  dụng từ  khóa synchronized (đồng bộ)  để  gọi một phương thức hiệu chỉnh (modified). Khi một luồng  đang  được thực thi trong   phạm vi một phương thức  đồng bộ (synchronized), bất kỳ luồng khác hoặc phương thức   198 Core Java
  11. đồng bộ khác mà cố gắng gọi nó trong cùng thể hiện sẽ phải đợi. Chương   trình   8.4   mo   tả   sự   làm   việc   của   từ   khóa   synchronized.   Ở   đây,   lớp  “Target” có  một phương thức “display()” mà  phương thức này lấy một tham số  kiểu số   nguyên (int). Số này được hiển thị trong phạm vi các cặp ký tự “ # số # ”. Phương   thức “Thread.sleep(1000) tạm dừng luồng hiện tại sau khi  phương thức “display()” được   gọi. Thiết lập (khởi dựng) của lớp “Source” lấy một tham chiếu  đến một  đối tượng “t”  của lớp “Target”, và một biến số nguyên. Ở đây, một luồng mới cũng được tạo ra. Luồng  này gọi phương thức run() của  đối tượng “t”. Lớp chính “Sync” tạo thể  hiện lớp “Target”   là “target và tạo ra 3 đối tượng của lớp “Source”. Cùng đối tượng “target” được truyền cho  mỗi  đối tượng “Source”. Phương thức “join()”  làm luồng  được gọi  đợi cho  đến khi luồng   thực thi xong. Chương trình 8.4 class Target { synchronized void display(int num) { System.out.print(" "+num); try{ Thread.sleep(1000); }catch(InterruptedException e){ System.out.println("Interrupted"); } System.out.println("  "); } }  class Source implements Runnable{ int number; Target target; Thread t; public Source(Target targ,int n){ target = targ; number = n; t = new Thread(this); t.start(); } public void run() { target.display(number); } Đa luồng (Multithreading) 199
  12. } class Sync { public static void main(String args[]){ Target target = new Target(); int digit = 10; Source s1 = new Source(target,digit++); Source s2 = new Source(target,digit++); Source s3 = new Source(target,digit++); try{ s1.t.join(); s2.t.join(); s3.t.join(); }catch(InterruptedException e){ System.out.println("Interrupted"); } } } Kết quả hiện thị như hình cho dưới đây: Hình 8.6 Kết quả hiện thị của chương trình 8.4 Trong chương trình trên, có  một “dãy số” liên tiếp  được hiển thị  “display()”.  Điều  này có nghĩa là việc thâm nhập bị hạn chế một luồng tại mỗi thời điểm. Nếu không có từ   khóa   synchronized   trong   định   nghĩa   phương   thức   “display()”   của   lớp   “Target”,   tất   cả  luồng trên có  thể  cùng lúc gọi cùng phương thức, trên cùng  đối tượng.  Điều kiện này   được biết đến như là race ­condition. Trong trường hợp này, kết quả sẽ như hình 8.7 200 Core Java
  13. Hình 8.7 Kết quả hiển thị của chương trình 8.7 không có sự đồng bộ 2.  Sử dụng khối đồng bộ (Synchronized Block) Tạo ra các phương thức synchronzed trong phạm vi các lớp là một phương pháp  dễ  và  có  hiệu quả  của việc thực hiện sự   đồng bộ. Tuy nhiên,  điều này không làm việc  trong tất cả các trường hợp. Hãy xem một trường hợp nơi mà ta muốn sự đồng bộ được thực hiện  với các đối  tượng của lớp mà không được thiết kế cho thực đa luồng. Tức là, lớp không sử dụng các   phương thức  đồng bộ. Hơn nữa, mã  nguồn là  không có. Vì  thế  từ  khoá  synchronized  không thể được thêm vào các phương thức thích hợp trong phạm vi lớp. Để   đồng bộ   được  đối tượng của lớp này, tất cả  các lời gọi phương thức mà  lớp   này  được  đặt bên trong một khối  đồng bộ. Tất cả  chúng sử  dụng chung một câu lệnh   đồng bộ được cho như sau: synchronized(object) { // các câu lệnh đồng bộ } Ở   đây, “object” là  một tham chiếu  đến  đối tượng  được  đồng bộ. Dấu ngoặc móc  không cấn thiết khi chỉ một câu lệnh được đồng bộ. Một khối đồng bộ bảo đảm rằng việc  gọi đến một phương thức của đối tượng chỉ được thực hiên sau khi luồng hiện hành đã sử  dụng xong phương thức. Chương trình 8.5 chỉ ra câu lệnh đồng bộ sử dụng như thế nào: Chương trình 8.5 class Target { void display(int num) { Đa luồng (Multithreading) 201
  14. System.out.print(" "+num); try{ Thread.sleep(1000); }catch(InterruptedException e){ System.out.println("Interrupted"); } System.out.println("  "); } }  class Source implements Runnable{ int number; Target target; Thread t; public Source(Target targ,int n){ target = targ; number = n; t = new Thread(this); t.start(); } // đồng bộ gọi phương thức display() public void run(){ synchronized(target) { target.display(number); } } } class Synchblock { public static void main(String args[]){ Target target = new Target(); int digit = 10; Source s1 = new Source(target,digit++); Source s2 = new Source(target,digit++); Source s3 = new Source(target,digit++); try{ s1.t.join(); s2.t.join(); s3.t.join(); 202 Core Java
  15. }catch(InterruptedException e){ System.out.println("Interrupted"); } } } Ở   đây, từ khóa synchronized không hiệu chỉnh phương thức “display()”. Từ  khóa   này  được sử  dụng trong phương thức run() của lớp “Target” (mục tiêu). Kết quả  xuất ra  màn hình tương tự với kết quả chỉ ra ở hình số 8.6 3. Ưu điểm của các phương thức đồng bộ Người lập trình thường viết các chương trình  đơn luồng. Tất nhiên một số trường   hợp nhất định  đa luồng là không hiệu quả. Ví dụ, nó không làm tăng hiệu năng của các   trình biên dịch. Trình biên dịch Java Sun không chứa nhiều phương thức đồng bộ. Các phương thức đồng bộ không thực thi tốt như là các phương thức không đồng   bộ. Các phương thức này chậm hơn từ ba đến bốn lần so với các phương thức tương ứng  không  đồng bộ. Trong trường hợp chúng ta cần hiệu năng cao thì  nên hạn chế sử dụng  các phương thức đồng bộ. 8.11 Cơ chế “wait­notify” (đợi – thông báo) Luồng chia các tác vụ  thành các  đơn vị  cụ  thể  và  logic.  Điều này thay thế  các   hình thức lập trình lặp sự kiện. Các luồng loại trừ “polling” (kiểm tra liên tục). Một vòng lặp dùng để kiểm tra điều kiện gọi là “polling”. Khi điều kiện nhận giá trị  là True (đúng), các câu lệnh tương ứng được thực hiện. Đây là tiến trình thường lãng phí   thời gian của CPU. Ví dụ, khi một luồng sinh ra một số dữ liệu, và các luồng khác đang  chi phối nó, luồng sinh ra phải  đợi cho  đến khi các luồng sử dụng nó hoàn thành, trước   khi phát sinh ra dữ liệu. Để  tránh trường hợp polling, Java bao gồm một cơ  chế  giao tiếp giữa các tiến  trình bằng các   phương thức “wait()”, “notify()”   và  “notifyAll()” . Các phương thức này  được thực hiện như là các các phương thức final trong lớp Object, vì vậy tất cả các lớp có  thể  thâm nhập chúng. Tất cả  3 phương thức này có  thể   được gọi chỉ  từ  trong phạm vi  một phương thức đồng bộ (synchronized). Các chức năng của phương thức “wait()”, “notify()”, và “notifyAll()” là:  Phương thức wait() làm cho luồng gọi nó  từ  bỏ  yêu cầu monitor, và  chuyển  sang trạng thái “sleep” (chờ) cho  đến khi luồng khác thôi monitor tài nguyên  nó cần  (đối tượng đang monitorgọi phương thức “notify()”).  Phương thức  notify()  đánh thức, hoặc thông báo cho luồng  đầu tiên mà   đã  gọi phương thức wait() trên cùng đối tượng.  Phương thức notifyAll() đánh thức, hoặc  thông báo tất cả  các luồng mà   đã   gọi phương thức wait() trên cùng đối tượng. Đa luồng (Multithreading) 203
  16.  Luồng có quyền ưu tiên cao nhất là luồng chạy đầu tiên. Cú pháp của 3 phương thức này như sau: final void wait() throws IOException final void notify() final void notifyAll() Các phương thức wait() và  notify() cho phép chia sẻ   đối tượng, làm tạm ngừng   luồng, khi  đối tượng trở  thành không còn giá  trị cho luồng. Chúng cũng cho phép luồng   tiếp tục khi thích hợp. Các luồng bản thân nó  không bao giờ  kiểm tra trạng thái của  đối tượng  đã  chia  sẻ. Một  đối tượng mà   điều khiển các luồng yêu cầu nó  theo kiểu này  được gọi là   monitor. Trong phạm vi Java, một monitor là  bất kỳ   đối tượng nào mà  có  mã   đồng bộ.   Các monitor  được sử  dụng cho các phương thức wait() và  notify(). Cả  hai phương thức   này phải được gọi trong mã đồng bộ. Một số điểm cần nhớ trong khi sử dụng phương thức wait():  Luồng  gọi trả  CPU  Luồng  gọi mở  khóa  Luồng  gọi đi vào vùng đợi của monitor. Các điểm chính cần nhớ về phương thức notify()  Một luồng vùng đợi của monitor chuyển sang trạng thái sẵn sàng.  Luồng mà đã được thông báo phải yêu cầu khóa  monitor trước khi nó có thể   bắt đầu.  Phương thức notify() là  không chính xác, vì  nó  không thể  chỉ  ra  được luồng   được thông báo. Trong một trạng thái đã trộn lẫn, luồng có thể thay đổi trạng  thái của monitor mà   điều này làm  ảnh hưởng  đến luồng  đã   được  đưa thông  báo. Trong trường hợp này, các phương thức của monitor  đưa ra 2 sự   đề  phòng: o Trạng thái của monitor sẽ   được kiểm tra trong một vòng lặp “while”  thay vì là câu lệnh if o Sau khi thay  đổi trạng thái của monitor, phương thức notifyAll() nên  được sử dụng thay vì notify(). Chương trình 8.6 biểu thị cho việc sử dụng các phương thức notify(0 và wait(): Chương trình 8.6  import java.applet.*; import java.awt.*; import java.awt.event.*; 204 Core Java
  17. /*  */ public class mouseApplet extends Applet implements MouseListener{ boolean click; int count; public void init() { super.init(); add(new clickArea(this)); //doi tuong ve duoc tao ra va them vao add(new clickArea(this));//doi tuong ve duoc tao ra va them vao addMouseListener(this); } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { synchronized (this) { click = true; notify(); } count++; //dem viec click Thread.currentThread().yield(); click = false; } public void mouseReleased(MouseEvent e) { } } //kết thúc Applet class clickArea extends java.awt.Canvas implements Runnable{ mouseApplet myapp; clickArea(mouseApplet mapp){ this.myapp = mapp; setSize(40,40); Đa luồng (Multithreading) 205
  18. new Thread(this).start(); } public void paint(Graphics g){ g.drawString(new Integer(myapp.count).toString(),15,20); } public void run(){ while(true){ synchronized (myapp) { while(!myapp.click){ try{ myapp.wait(); }catch(InterruptedException ie){ } } } repaint(250); } }//end run } Không cần các phương thức wait() và notify(), canvas không thể biết khi nào cập  nhập hiển thị. Kết quả xuất ra ngoài của chương trình được đưa ra như sau: Hình 8.8 Kết quả sau mỗi lần kích chuột 8.12 Khoá chết (Deadlocks) Một “deadlock” xảy ra khi hai luồng có một phụ thuộc vòng  trên một cặp  đối tượng đồng bộ; ví dụ, khi một luồng thâm nhập vào monitor trên đối tượng  “ObjA”, và  206 Core Java
  19. một luồng khác thâm nhập vào monitor trên đối tượng “ObjB”. Nếu luồng trong “ObjA” cố   gắng gọi phương thức đồng bộ trên “ObjB”, khoá chết xảy ra. Khó để tìm ra khóa chết bởi những nguyên nhân sau:  Nó hiểm khi xảy ra, khi hai luồng chia nhỏ thời gian thực thi cùng lúc  Nó liên quan đến nhiều hơn hai luồng và hai đối tượng đồng bộ Nếu một chương trình  đa luồng bị  treo thường xuyên, ngay lập tức kiểm tra lại   điều kiện gây ra khoá chết. Chương trình 8.7 tạo ra  điều kiện khoá  chết. Lớp chính bắt  đầu 2 luồng. Mỗi  luồng   gọi  phương   thức  đồng   bộ   run().   Khi   luồng  “t1”    thức  dậy,  nó  gọi   phương  thức   “synchIt()” của đối tượng deadlock “dlk2”. Khi luồng “t1” monitor “dlk2”, luồng “t1” bắt đầu  đợi monitor. Khi luồng “t2” thức, nó  cố  gắng gọi phương thức “synchIt()” của  đối tượng   Deadlock “dlk1”. Bây giờ, “t2” cũng phải  đợi, bởi vì   đây là trường hợp tương tự với luồng  “t1”. Từ   đó, cả hai luồng  đang  đợi lẫn nhau, cả hai sẽ không bao giờ  thức  được.  Đây là   điều kiện khoá chết. Chương trình 8.7 public class Deadlock implements Runnable{ public static void main(String args[]){ Deadlock dlk1= new Deadlock(); Deadlock dlk2 = new Deadlock(); Thread t1 = new Thread(dlk1); Thread t2 = new Thread(dlk2); dlk1.grabIt = dlk2; dlk2.grabIt = dlk1; t1.start(); t2.start(); System.out.println("Started"); try{ t1.join(); t2.join(); }catch(InterruptedException e){ System.out.println("error occured"); } System.exit(0); } Deadlock grabIt; public synchronized void run() { try{ Thread.sleep(1500); Đa luồng (Multithreading) 207
  20. }catch(InterruptedException e){ System.out.println("error occured"); } grabIt.syncIt(); } public synchronized void syncIt() { try{ Thread.sleep(1500); System.out.println("Sync"); }catch(InterruptedException e){ System.out.println("error occured"); } System.out.println("In the syncIt() method"); } } Kết quả của chương trình này được hiển thị như sau: Hình 8.9 Khoá chết 8.13 Thu dọn “rác” (Garbage collection) Thu dọn “rác” (Garbage collection) cải tạo hoặc làm trống bộ nhớ đã cấp cho các   đối tượng mà  các  đối tượng này không sử  dụng trong thời gian dài. Trong ngôn ngữ  lập  trình hướng đối tượng khác như C++, lập trình viên phải tự giải phóng. Thất bại trong việc  giải phóng bộ nhớ có thể gây ra một số hậu quả. Java tự động tiến hành thu dọn rác để  cung cấp giải pháp cho vấn đề này. Một đối tượng trở nên thích hợp cho sự dọn rác nếu  không có tham chiếu đến nó, hoặc nếu nó được gán bằng null. Trình thực thi dọn rác là một luông chạy ngầm (deamon) co mức ưu tiên thấp. Ta  208 Core Java
ADSENSE

CÓ THỂ BẠN MUỐN DOWNLOAD

 

Đồng bộ tài khoản
2=>2