Lập trình Java cơ bản - Chương 8: Đa luồng
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 hiểm (daemon thread)
Giải thích thiết lập các luồng ưu tiên như thế nào
Giải thích được sự 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 (how to apply synchronized keywords)
Liệt kê những điểm yếu của sự đồng bộ
Giải thích vai trò của các phương thức wait() (đợi), notify() (thông báo) và notifyAll().
Mô tả một điều kiện bế tắc (deadlock condition)
lặp “while” tốt hơn là câu lệnh if Sau khi thay đổi trạng thái của monitor, phương thức notifyAll() sẽ được sử dụng, tốt hơn phương thức 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.*; /* */ 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); 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(), luồng bức vẽ (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 Sự bế tắt (Deadlocks) Một “deadlock” (sự bế tắt) xảy ra khi hai luồng có một phụ thuộc vòng quanh trên một cặp đối tượng đồng bộ; lấy ví dụ, khi một luồng thâm nhập vào monitor trên đối tượng “ObjA”, và 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”, một bế tắt xảy ra. Nó khó để gỡ lỗi một bế tắ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 trong cùng một con đường Nó có thể bao hàm 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 khóa kín thường xuyên, ngay lập tức kiểm tra lại điều kiện bế tắt. Chương trình 8.7 tạo ra điều kiện bế tắt. Lớp chính (main) bắt đầu 2 luồng. Mỗi luồng gọi phương thức đồng bộ run(). Khi luồng “t1” đánh thức, nó gọi phương thức “synchIt()” của đối tượng deadlock “dlk1”. Từ đó luồng “t2” một mình giám sát cho “dlk2”, luồng “t1” bắt đầu đợi monitor. Khi luồng “t2” đánh thức, nó cố gắng gọi phương thức “synchIt()” của đối tượng Deadlock “dlk2”. 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ẽ đánh thức. Đây là điều kiện bế tắ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 = dlk1; dlk2.grabIt = dlk2; 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); }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 Sự bế tắt 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ớ đã định vị 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 làm cho bộ nhớ trống mà đã không được yêu cầu trong thời gian dài. Tình trạng không hoạt động để bộ nhớ trống có thể là kết quả trong một số vấn đề. Java tự động tiến trình thu dọn rác để cung cấp giải pháp duy nhất 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ó đã đăng ký rỗng. Sự dọn rác thực thi như là một luồng riêng biệt có quyền ưu tiên thấp. Bạn có thể viện dẫn một phương thức gc() của thể nghiệm để viện dẫn sự dọn rác. Tuy nhiên, bạn không thể dự đoán hoặc bảo đảm rằng sự dọn rác sẽ thực thi một cách trọn vẹn sau đó. Sử dụng câu lện sau để tắt đi sự dọn rác trong ứng dụng: Java –noasyncgc …. Nếu chúng ta tắt đi sự dọn rác, chương trình hầu như chắc chắn rằng bị treo do bởi việc đó. Phương thức finalize() (hoàn thành) Java cung cấp một con đường để làm sạch một tiến trình trước khi điều khiển trở lại hệ điều hành. Điều này tương tự như phương thức phân hủy của C++ Phương thức finalize(), nếu hiện diện, sẽ được thực thi trên mỗi đối tượng, trước khi sự dọn rác. Câu lệnh của phương thức finalize() như sau: protected void finalize() throws Throwable Tham chiếu không phải là sự dọn rác; chỉ các đối tượng mới được dọn rác Lấy thể nghiệm: Object a = new Object(); Object b = a; a = null; Ở đây, nó sẽ sai khi nói rằng “b” là một đối tượng. Nó chỉ là một đối tượng tham chiếu. Hơn nữa, trong đoạn mã trích trên mặc dù “a’ được đặt là rỗng, nó không thể được dọn rác, bởi vì nó vẫn còn có một tham chiếu (b) đến nó. Vì thế “a” vẫn còn với đến được, thật vậy, nó vẫn còn có phạn vi sử dụng trong phạm vi chương trình. Ở đây, nó sẽ không được dọn rác. Tuy nhiên, trong ví dụ cho dưới đây, giả định rằng không có tham chiếu đến “a” tồn tại, đối tượng “a” trở nên thích hợp cho garbage collection. Object a = new Object(); … … … a = null; Một ví dụ khác: Object m = new Object(); Object m = null; Đối tượng được tạo ra trong sự bắt đầu có hiệu lực cho garbage collection Object m = new Object(); M = new Object(); Bây giờ, đối tượng căn nguyên có hiệu lực cho garbage collection, và một đối tượng mới tham chiếu bởi “m” đang tồn tại. Bạn có thể chạy phương thức garbage collection, nhưng không có banỏ đảm rằng nó sẽ xảy ra. Chương trình 8.8 điển hình cho garbage collection. Chương trình 8.8 class GCDemo { public static void main(String args[]) { int i; long a; , Runtime r=Runtime.getRuntimeO; Long valuesD =new Long[200]; System. out. print In ("Amount of free memory is" + r.freeMemoryO); r.gcO; System.out.println("Amount of free memory after garbage collection is " + r.freeMemoryO); for (a=IOOOO.i=O;i<200;a++.i++) { values[i] =new Long(a); } System.out.println("Amount of free memory after creating the array " + r.freeMemoryO); for (i=O;i<200;i++) { values[i] =null; } System.out.println("Arnount of free memory after garbage collection is " + r.freeMemoryO); } Chúng ta khai một mảng gồm 200 phần tử, trong đó kiểu dữ liệu là kiểu Long. Trước khi mảng được tạo ra, chúng ta phải xác định rõ số lượng bộ nhớ trống, và hiển thị nó. Rồi thì chúng ta viện dẫn phương thức gc() của thể nghiệm Runtime (thời gian thực thi) hiện thời. Điều này có thể hoặc không thể thực thi garbage collection. Rồi thì chúng ta tạo ra mảng, và đang ký giá trị cho các phần tử của mảng. Điều này sẽ giảm bớt số lượng bộ nhớ trống. Để làm các mảng phần tử thích hợp cho garbage collection, chúng ta đặt chúng rỗng. Cuối cùng, chúng ta sử dụng phương thức gc() để viện dẫn garbage collection lần nữa. Kết quả xuất ra màn hình của chương trình trên như sau: Hình 8.10 Garbage collection Tổng kết Một luồng là đơn vị nhỏ nhất của đoạn mã thực thi được mà một tác vụ riêng biệt. Đa luồng giữ cho thời gian rỗi là nhỏ nhất. Điều này cho phép bạn viết các chương trình có khả năng sử dụng tối đa CPU. Luồng bắt đầu thực thi sau khi phương thức start() được gọi Lập trình viên, máy ảo Java, hoặc hệ điều hành bảo đảm rằng CPU được chia sẻ giữa các luồng. Có hai loại luồng trong một chương trình Java: Luồng người dùng Luồng hiểm. Một nhóm luồng là một lớp mà nắm bắt một nhóm các luồng. Đồng bộ cho phép chỉ một luồng thâm nhập một tài nguyên được chia sẻ tại một thời điểm. Để tránh kiểm soát vòng, Java bao gồm một thiết kế tốt trong tiến trình kỹ thuật truyền thông sử dụng các phương thức “wait()” (đợi), “notify()” (thông báo) và “notifyAll()” (thông báo hết). Một “bế tắt” xảy ra khi hai luồng có mọt phụ thuộc xoay vòng trên một phần của các đối tượng đồng bộ Garbage collection là một tiến trình nhờ đó bộ nhớ được định vị để các đối tượng mà không sử dụng trong thời gian dài, có thể cải tạo hoặc làm rãnh bộ nhớ. Kiểm tra lại sự hiểu biết của bạn Một ứng dụng có thể chứa đựng nhiều luồng Đúng/Sai Các luồng con được tạo ra từ luồng chính Đúng/Sai Mỗi luồng trong một chương trình Java được đăng ký một quyền ưu tiên mà máy ảo Java có thể thay đổi. Đúng/Sai Phương thức____________ có thể tạm thời ngừng việc thực thi luồng Mặc định, một luồng có một quyền ưu tiên ________ một hằng số của _______ _________ luồng được dùng cho các luồng “nền”, cung cấp dụch vụ cho luồng khác. Trong luồng đồng bộ, một __________ là một đối tượng mà được sử dụng như là một khóa riêng biệt lẫn nhau. ___________ thường thực thi bởi một vòng lặp mà được sử dụng để lặp lại việc kiểm tra một số điều kiện. Bài tập: Viết một chương trình mà hiển thị một sự đếm lùi từng giây cho đến không, như hình sau: Ban đầu, số 300 sẽ được hiển thị. Giá trị sẽ được giảm dần cho đến 1 đến khi ngoài giá trị 0. Giá trị sẽ được trả lại 300 một lần nữa giảm đến trở thành 0. Viết một chương trình mà hiển thị như hình dưới đây: Tạo 3 luồng và một luồng chính trong “main”. Thực thi mỗi luồng như một chương trình thực thi. Khi chương trình kết thúc, các câu lệnh thoát cho mỗi luồng sẽ được hiển thị. Sử dụng kỹ thuật nắm bắt lỗi.
File đính kèm:
- Lập trình Java cơ bản - Chương 8_Đa luồng.doc