Label

Thứ Năm, 7 tháng 4, 2011

Tìm hiểu Thread trong JAVA – Phần 5

Tìm hiểu Thread trong JAVA – Phần 5

Bài 4: Đồng bộ hóa(Synchronized) Thread (tiếp theo)
3.Static synchronized method:
Ngoài lock ở cấp độ đối tượng(object-level) cho mỗi instance của lớp, còn có lock ở cấp độ lớp (class-level) được chia sẽ cho tất cả instance của một lớp cụ thể. Mỗi lớp nạp bởi JavaVM có đúng một lock class-level. Nếu một phương thức được khai báo cả 2 từ khóa static và synchronized, một thread phải có được quyền truy cập vào lock class-level trước khi đi vào phương thức.
Lock class-level có thể được sử dụng để truy cập độc quyền vào các biến thành viên static. Cũng như lock object-level cần được ngăn chặn sự sửa đổi dữ liệu của các biến thành viên non-static, lock class-level cũng cần được ngăn chặn sự sửa đổi của các biến thành viên static. Thậm chí là khi không có biến nào liên quan, thì modifier synchronized cũng có thể được sử dụng trên các phương thức static nhằm đảm bảo chỉ có một thread nằm bên trong một phương thức tại một thời điểm.
Để hiểu rõ thêm về vấn đề này, ta xét ví dụ sau:
1: public class StaticNeedSync extends Object {
2:     private static int nextSerialNum = 10001;
3:
4:     public static int getNextSerialNum() {
5:         int sn = nextSerialNum;
6:
7:         // Simulate a delay that is possible if the thread
8:         // scheduler chooses to swap this thread off the
9:         // processor at this point. The delay is exaggerated
10:         // for demonstration purposes.
11:         try { Thread.sleep(1000); }
12:         catch ( InterruptedException x ) { }
13:
14:         nextSerialNum++;
15:         return sn;
16:     }
17:
18:     private static void print(String msg) {
19:         String threadName = Thread.currentThread().getName();
20:         System.out.println(threadName + “: “ + msg);
21:     }
22:
23:     public static void main(String[] args) {
24:         try {
25:             Runnable r = new Runnable() {
26:                     public void run() {
27:                         print(“getNextSerialNum()=” +
28:                                 getNextSerialNum());
29:                     }
30:                 };
31:
32:             Thread threadA = new Thread(r, “threadA”);
33:             threadA.start();
34:
35:             Thread.sleep(1500);
36:
37:             Thread threadB = new Thread(r, “threadB”);
38:             threadB.start();
39:
40:             Thread.sleep(500);
41:
42:             Thread threadC = new Thread(r, “threadC”);
43:             threadC.start();
44:
45:             Thread.sleep(2500);
46:
47:             Thread threadD = new Thread(r, “threadD”);
48:             threadD.start();
49:         } catch ( InterruptedException x ) {
50:             // ignore
51:         }
52:     }
53: }
Lớp StaticNeedSync có một biến thành viên nextSerialNum với khai báo private static, được dùng để lưu giá trị các số tiếp theo sẽ được tạo ra (dòng 2). Phương thức getNextSerialNum ()  (dòng 4-16) là được khai báo public và static. Khi gọi phương thức này, nó có giá trị hiện tại của nextSerialNum và lưu trữ nó trong một biến cục bộ sn (dòng 5). Sau đó, thread gọi phương thức này tạm sleep 1 giây(dòng 11) để một thread thứ 2 thực hiện. Khi các thread nhận được một cơ hội để chạy lại, nó tăng nextSerialNum lên 1 biến để chuẩn bị cho cuộc gọi tiếp theo (dòng 14).
Main thread có 4 thread tương tác với phương thức getNextSerialNum(). Cả 4 thread đều sử dụng cùng một đối tượng Runnable(dòng 25-30). Main thread bắt đầu với threadA(dòng 33) và sleep khoảng 1,5 giây. Thời gian này đủ để threadA vào và trả về từ phương thức getNextSerialNum(). Tiếp theo, main Thread tiếp tục bắt đầu với threadB(dòng 38), sau đó nó sleep khoảng 0,5 giây(dòng 40) trước khi nó bắt đầu threadC(dòng 43). Cả threadB và threadC cùng nằm trong phương thức getNextSerialNum(), và điều này làm nảy sinh một vài vấn đề. Sau khi chờ 2,5 giây (nhiều thời gian cho threadB và threadC trả về), main thread bắt đầu threadD (dòng 45-48). threadD gọi phương thức getNextSerialNum () lần cuối cùng. Kết quả dưới đây mô tả ví dụ trên:
threadA: getNextSerialNum()=10001
threadB: getNextSerialNum()=10002
threadC: getNextSerialNum()=10002
threadD: getNextSerialNum()=10004
Để giải quyết vấn đề trên, ta chỉ cần thêm vào từ khóa synchronized vào phương thức getNextSerialNum().
1: public class StaticSync extends Object {
2:     private static int nextSerialNum = 10001;
3:
4:     public static synchronized int getNextSerialNum() {
5:         int sn = nextSerialNum;
6:
7:         // Simulate a delay that is possible if the thread
8:         // scheduler chooses to swap this thread off the
9:         // processor at this point. The delay is exaggerated
10:         // for demonstration purposes.
11:         try { Thread.sleep(1000); }
12:         catch ( InterruptedException x ) { }
13:
14:         nextSerialNum++;
15:         return sn;
16:     }
17:
18:     private static void print(String msg) {
19:         String threadName = Thread.currentThread().getName();
20:         System.out.println(threadName + “: “ + msg);
21:     }
22:
23:     public static void main(String[] args) {
24:         try {
25:             Runnable r = new Runnable() {
26:                     public void run() {
27:                         print(“getNextSerialNum()=” +
28:                                 getNextSerialNum());
29:                     }
30:                 };
31:
32:             Thread threadA = new Thread(r, “threadA”);
33:             threadA.start();
34:
35:             Thread.sleep(1500);
36:
37:             Thread threadB = new Thread(r, “threadB”);
38:             threadB.start();
39:
40:             Thread.sleep(500);
41:
42:             Thread threadC = new Thread(r, “threadC”);
43:             threadC.start();
44:
45:             Thread.sleep(2500);
46:
47:             Thread threadD = new Thread(r, “threadD”);
48:             threadD.start();
49:         } catch ( InterruptedException x ) {
50:             // ignore
51:         }
52:     }
53: }
Kết quả từ ví dụ trên:
threadA: getNextSerialNum()=10001
threadB: getNextSerialNum()=10002
threadC: getNextSerialNum()=10003
threadD: getNextSerialNum()=10004
Sở dĩ vấn đề trên được giải quyết là do khi threadC vào phương thức getNextSerialNum(), nó lập tức rơi vào trạng thái block và đợi threadB kết thúc nó mới được phép tiếp tục thực hiện trong phương thức getNextSerialNum().
4.Sử dụng Class-level lock trong synchronized statement.
Để sử dụng synchronized statement trong Class-level lock, ta sử dụng theo cú pháp sau:
synchronized ( ClassName.class ) {
// body
}
Ví dụ: ta tạo lớp StaticBlock để mô phỏng kỹ thuật trên như sau:
1: public class StaticBlock extends Object {
2:     public static synchronized void staticA() {
3:         System.out.println(“entering staticA()”);
4:
5:         try { Thread.sleep(5000); }
6:         catch ( InterruptedException x ) { }
7:
8:         System.out.println(“leaving staticA()”);
9:     }
10:
11:     public static void staticB() {
12:         System.out.println(“entering staticB()”);
13:
14:         synchronized ( StaticBlock.class ) {
15:             System.out.println(
16:                     “in staticB() – inside sync block”);
17:
18:             try { Thread.sleep(2000); }
19:             catch ( InterruptedException x ) { }
20:         }
21:
22:         System.out.println(“leaving staticB()”);
23:     }
24:
25:     public static void main(String[] args) {
26:         Runnable runA = new Runnable() {
27:                 public void run() {
28:                     StaticBlock.staticA();
29:                 }
30:             };
31:
32:         Thread threadA = new Thread(runA, “threadA”);
33:         threadA.start();
34:
35:         try { Thread.sleep(200); }
36:         catch ( InterruptedException x ) { }
37:
38:         Runnable runB = new Runnable() {
39:                 public void run() {
40:                     StaticBlock.staticB();
41:                 }
42:             };
43:
44:         Thread threadB = new Thread(runB, “threadB”);
45:         threadB.start();
46:     }
47: }
Trong lớp StaticBlock, phương thức staticA() được khai báo synchronized và static. Phương thức staticB() được khai báo static và có chứa một block synchronized(dòng 14-20). Các đối tượng sử dụng để kiểm soát truy cập vào block này là đối tượng Class cho StaticBlock và được tìm thấy bằng cách sử StaticBlock.class(dòng 14).
Trong phương thức main, threadA được bắt đầu và gọi phương thức staticA()(dòng 28). Sau một khoảng thời gian 200 mili giây, threadB bắt đầu và gọi phương thức staticB(). Trong khi threadA sleep trong phương thức staticA(), threadB vào phương thức staticB(), in một message và đi vào block static synchronized(dòng 14).
Khi threadA trả về từ staticA(), threadB nhận class-level lock và hoàn tất phương thức staticB().
Sau đây là kết quả của ví dụ trên:
1: entering staticA()
2: entering staticB()
3: leaving staticA()
4: in staticB() – inside sync block
5: leaving staticB()
Lưu ý rằng, mặc dù threadB có thể vào phương thức staticB()(dòng 2) nhưng nó không thể vào để thực hiện block synchronized(dòng 4) cho đến khi threadA trả về từ staticA()(dòng 3). ThreadB sẽ rơi vào trạng thái block cho tới khi threadA giải phóng class-level lock.
5.Deadlocks:
Khi sử dụng nhiều thread truy cập vào các đối tượng có giữ lock, nếu không cẩn thận thì rất có thể xảy ra tình trạng deadlocks. Đó là khi một threadA nắm giữ lock1, một threadB nắm giữ lock2. Trong khi threadA nắm giữ lock1, nó lại muốn nắm giữ thêm lock2, nhưng vì threadB đang nắm giữ lock2 nên threadA sẽ rơi vào trạng thái block, và nó phải đợi cho tới khi threadB giải phóng lock2. Tuy nhiên, vào thời điểm đó, trong khi threadB đang nắm giữ lock2, nó lại muốn tiếp tục nắm giữ lock1, và lock1 đang được nắm giữ bởi threadA, nên threadB lại rơi vào trạng thái block. Lúc này, cả threadA và threadB rơi vào trạng thái block mãi mãi vì phải đợi thread kia giải phóng lock. Đây chính là trường hợp deadlocks.
Ví dụ:
1: public class Deadlock extends Object {
2:     private String objID;
3:
4:     public Deadlock(String id) {
5:         objID = id;
6:     }
7:
8:     public synchronized void checkOther(Deadlock other) {
9:         print(“entering checkOther()”);
10:
11:         // simulate some lengthy process
12:         try { Thread.sleep(2000); }
13:         catch ( InterruptedException x ) { }
14:
15:         print(“in checkOther() – about to “ +
16:                 “invoke ‘other.action()’”);
17:         other.action();
18:
19:         print(“leaving checkOther()”);
20:     }
21:
22:     public synchronized void action() {
23:         print(“entering action()”);
24:
25:         // simulate some work here
26:         try { Thread.sleep(500); }
27:         catch ( InterruptedException x ) { }
28:
29:         print(“leaving action()”);
30:     }
31:
32:     public void print(String msg) {
33:         threadPrint(“objID=” + objID + “ – “ + msg);
34:     }
35:
36:     public static void threadPrint(String msg) {
37:         String threadName = Thread.currentThread().getName();
38:         System.out.println(threadName + “: “ + msg);
39:     }
40:
41:     public static void main(String[] args) {
42:         final Deadlock obj1 = new Deadlock(“obj1”);
43:         final Deadlock obj2 = new Deadlock(“obj2”);
44:
45:         Runnable runA = new Runnable() {
46:                 public void run() {
47:                     obj1.checkOther(obj2);
48:                 }
49:             };
50:
51:         Thread threadA = new Thread(runA, “threadA”);
52:         threadA.start();
53:
54:         try { Thread.sleep(200); }
55:         catch ( InterruptedException x ) { }
56:
57:         Runnable runB = new Runnable() {
58:                 public void run() {
59:                     obj2.checkOther(obj1);
60:                 }
61:             };
62:
63:         Thread threadB = new Thread(runB, “threadB”);
64:         threadB.start();
65:
66:         try { Thread.sleep(5000); }
67:         catch ( InterruptedException x ) { }
68:
69:         threadPrint(“finished sleeping”);
70:
71:         threadPrint(“about to interrupt() threadA”);
72:         threadA.interrupt();
73:
74:         try { Thread.sleep(1000); }
75:         catch ( InterruptedException x ) { }
76:
77:         threadPrint(“about to interrupt() threadB”);
78:         threadB.interrupt();
79:
80:         try { Thread.sleep(1000); }
81:         catch ( InterruptedException x ) { }
82:
83:         threadPrint(“did that break the deadlock?”);
84:     }
85: }
Trong phương thức main(), có hai instance của Deadlock được tạo ra và hai thread được bắt đầu, mỗi thread chạy trên một instance; instance thứ nhất được thiết lập là obj1, cái kia là obj2(dòng 42-43). ThreadA được start và gọi phương thức checkOther() của obj1 và gán một tham chiếu đến obj2(dòng 47), sau khi sleep khoảng 200 mili giây, threadB được start và gọi phương thức checkOther() của obj2 và nó gán một tham chiếu tới obj1. Phương thức checkOther() là synchronized nên thread truy cập vào nó sẽ nhận lock. Cả threadA và threadB sẽ không bao giờ thực hiện được phương thức action(), vì khi threadA đang ở trong phương thức checkOther() nó nắm giữ lock trên obj1, thì threadB cũng ở trong phương thức checkOther() và nắm giữ lock của obj2. Khi threadA cố gắng gọi phương thức action() trong obj2, nó sẽ bị block do threadB đang nắm giữ lock trên obj2, và nó sẽ đợi cho tới khi threadB giải phóng lock trên obj2. Sau đó, threadB gọi phương thức action() trên obj1 và cũng rơi vào trạng thái block do lock trên obj1 đã bị threadA chiếm giữ; main thread sleep trong 5 giây khi deadlock được tạo ra(dòng 66), khi tỉnh dậy, nó cố gắng phá vỡ deadlock bằng cách interrupt threadA(dòng 72), sau 1 giây nó tiếp tục cố gắng interrupt threadB. Thật không may, điều này không phá vỡ deadlock, bởi khi một thread đang bị block vì đợi để có được lock, nó không đáp ứng với interrupt.
Kết quả từ ví dụ trên:
threadA: objID=obj1 – entering checkOther()
threadB: objID=obj2 – entering checkOther()
threadA: objID=obj1 – in checkOther() – about to invoke ‘other.action()’
threadB: objID=obj2 – in checkOther() – about to invoke ‘other.action()’
main: finished sleeping
main: about to interrupt() threadA
main: about to interrupt() threadB
main: did that break the deadlock?


http://mobilesprogramming.wordpress.com/2010/08/12/tim-hi%E1%BB%83u-thread-trong-java-ph%E1%BA%A7n-5/

Tìm hiểu Thread trong JAVA – Phần 4

Tìm hiểu Thread trong JAVA – Phần 4

Bài 4: Đồng bộ hóa(Synchronized) Thread
Khi nhiều thread cùng tương tác với một đối tượng, bạn cần phải điều khiển chúng một cách cẩn thận để tránh tranh chấp tài nguyên. Bài này giới thiệu những vấn đề có giới thiệu các lỗi thường gặp trong ứng dụng của bạn, khi nào và làm thế nào để sử dụng từ khóa synchronized để điều khiển việc truy cập vào các đối tượng và các biến cùng một thời điểm.
Việc bổ xung từ khóa synchronized vào khai báo phương thức nhằm đảm bảo chỉ có một thread được phép ở bên trong phương thức tại một thời điểm. Trước khi bạn học cách làm thế nào để định nghĩa một method đồng bộ hóa trong chương trình của bạn, hãy xem những gì xảy ra nếu việc đồng bộ hoá không được sử dụng trong một chương trình.
Ví dụ 1: mô phỏng 2 thread cùng truy cập đồng thời vào một phương thức của cùng một đối tượng.
1: public class BothInMethod extends Object {
2:     private String objID;
3:
4:     public BothInMethod(String objID) {
5:         this.objID = objID;
6:     }
7:
8:     public void doStuff(int val) {
9:         print(“entering doStuff()”);
10:         int num = val * 2 + objID.length();
11:         print(“in doStuff() – local variable num=” + num);
12:
13:         // slow things down to make observations
14:         try { Thread.sleep(2000); }
catch ( InterruptedException x ) { }
15:
16:         print(“leaving doStuff()”);
17:     }
18:
19:     public void print(String msg) {
20:         threadPrint(“objID=” + objID + “ – “ + msg);
21:     }
22:
23:     public static void threadPrint(String msg) {
24:         String threadName = Thread.currentThread().getName();
25:         System.out.println(threadName + “: “ + msg);
26:     }
27:
28:     public static void main(String[] args) {
29:         final BothInMethod bim = new BothInMethod(“obj1”);
30:
31:         Runnable runA = new Runnable() {
32:                 public void run() {
33:                     bim.doStuff(3);
34:                 }
35:             };
36:
37:         Thread threadA = new Thread(runA, “threadA”);
38:         threadA.start();
39:
40:         try { Thread.sleep(200); }
catch ( InterruptedException x ) { }
41:
42:         Runnable runB = new Runnable() {
43:                 public void run() {
44:                     bim.doStuff(7);
45:                 }
46:             };
47:
48:         Thread threadB = new Thread(runB, “threadB”);
49:         threadB.start();
50:     }
51: }
Trong phương thức main(), đối tượng BothInMethod được khởi tạo với một identifier là obj1(dòng 29). Tiếp theo, 2 thread được tạo ra để truy cập đồng thời vào phương thức doStuff(). Thread đầu tiên có tên là threadA, cái thứ hai là threadB. Sau khi threadA bắt đầu(dòng 38), nó gọi doStuff() và truyền vào giá trị 3(dòng 33). Khoảng 200 mili giây sau, threadB được bắt đầu và gọi phương thức doStuff() trên cùng một đối tượng và truyền vào giá trị 7.
Cả threadA và threadB cùng ở trong phương thức doStuff()(từ dòng 8->17) tại cùng một thời điểm; threadA vào trước, sau 200 mili giây thì threadB vào. Bên trong doStuff(), biến cục bộ num được tính toán thông qua tham số val và biến thành viên objID(dòng 10). Bởi vì, threadA và threadB đều gán một biến val khác nhau, nên giá trị biến num sẽ khác nhau cho mỗi thread. Phương thức sleep() được sử dụng trong doStuff() nhằm làm chậm lại để đảm bảo rằng cả hai thread đều ở trong cùng một phương thức của cùng một đối tượng một cách đồng thời.
Lưu ý: Nếu hai hay nhiều thread đều ở trong một phương thức đồng thời, thì mỗi thread phải có mỗi bảo sao chép các biến cục bộ.
Kết quả từ ví dụ trên:
threadA: objID=obj1 – entering doStuff()
threadA: objID=obj1 – in doStuff() – local variable num=10
threadB: objID=obj1 – entering doStuff()
threadB: objID=obj1 – in doStuff() – local variable num=18
threadA: objID=obj1 – leaving doStuff()
threadB: objID=obj1 – leaving doStuff()
Các thread được đồng bộ hoá trong Java sử dụng thông qua một monitor. Hãy nghĩ rằng, một monitor là một object cho phép một thread truy cập vào một tài nguyên. Chỉ có một thread sử dụng một monitor vào bất kỳ một khoảng thời gian nào. Các lập trình viên nói rằng, các thread sở hữu monitor vào thời gian đó. Monitor cũng được gọi là một semaphore.
Như chúng ta đã biết, khái niệm semaphore (monitor được Tony Hoare đề xuất) thường được sử dụng để điều khiển đồng bộ các hoạt động truy cập vào những tài nguyên dùng chung. Một luồng muốn truy cập vào một tài nguyên dùng chung (như biến dữ liệu) thì trước tiên nó phải yêu cầu để có được monitor riêng. Khi có được monitor thì luồng như có được “chìa khóa” để “mở cửa” vào miền “tranh chấp” (tài nguyên dùng chung) để sử dụng những tài nguyên đó.
Cơ chế monitor thực hiện hai nguyên tắc đồng bộ chính:
  • Không một luồng nào khác được phân monitor khi có một luồng đã yêu cầu và đang chiếm giữ. Những luồng có yêu cầu monitor sẽ phải chờ cho đến khi monitor được giải phóng.
  • Khi có một luồng giải phóng (ra khỏi) monitor, một trong số các luồng đang chờ monitor có thể truy cập vào tài nguyên dùng chung tương ứng với monitor đó.
Cuối cùng, tác nhiệm của việc yêu cầu một monitor xảy ra đằng sau “màn chắn” trong Java. Java xử lý tất cả các chi tiết đó cho bạn. Bạn phải đồng bộ hoá các thread trong chương trình của bạn nếu như có nhiều hơn một thread sử dụng cùng một tài nguyên.
Trong lập trình có hai cách để thực hiện đồng bộ:
  • Các hàm (hàm) được đồng bộ
  • Các khối được đồng bộ
1.Các hàm được đồng bộ(synchronized method)
Nếu method được đồng bộ hoá là một instance method(phân biệt với static method đối với class), thì method được đồng bộ hóa sẽ kích hoạt lock đi kèm với đối tượng của phương thức đó. Ngược lại, nếu method được đồng bộ hóa là static thì nó kích hoạt lock đi kèm với class định nghĩa method được đồng bộ hoá.
Ví dụ 2: Mô phỏng sử dụng từ khóa synchronized, và chỉ có một thread ở trong một phương thức tại một thời điểm.
1: public class OnlyOneInMethod extends Object {
2:     private String objID;
3:
4:     public OnlyOneInMethod(String objID) {
5:         this.objID = objID;
6:     }
7:
8:     public synchronized void doStuff(int val) {
9:         print(“entering doStuff()”);
10:         int num = val * 2 + objID.length();
11:         print(“in doStuff() – local variable num=” + num);
12:
13:         // slow things down to make observations
14:         try { Thread.sleep(2000); }
catch ( InterruptedException x ) { }
15:
16:         print(“leaving doStuff()”);
17:     }
18:
19:     public void print(String msg) {
20:         threadPrint(“objID=” + objID + “ – “ + msg);
21:     }
22:
23:     public static void threadPrint(String msg) {
24:         String threadName = Thread.currentThread().getName();
25:         System.out.println(threadName + “: “ + msg);
26:     }
27:
28:     public static void main(String[] args) {
29:         final OnlyOneInMethod ooim = new OnlyOneInMethod(“obj1”);
30:
31:         Runnable runA = new Runnable() {
32:                 public void run() {
33:                     ooim.doStuff(3);
34:                 }
35:             };
36:
37:         Thread threadA = new Thread(runA, “threadA”);
38:         threadA.start();
39:
40:         try { Thread.sleep(200); }
catch ( InterruptedException x ) { }
41:
42:         Runnable runB = new Runnable() {
43:                 public void run() {
44:                     ooim.doStuff(7);
45:                 }
46:             };
47:
48:         Thread threadB = new Thread(runB, “threadB”);
49:         threadB.start();
50:     }
51: }
Ở ví dụ trên, ta thấy trong phương thức doStuff() chỉ có một thread tại một thời điểm; threadA đi vào(dòng 1) và đi ra(dòng 3) khỏi phương thức doStuff() trước khi threadB được phép vào(dòng 4). Việc sử dụng modifier synchronized bảo vệ phương thức doStuff() và chỉ cho phép một thread ở trong nó tại một thời điểm.
Dưới đây là kết quả của ví dụ 2, bạn có thể so sánh kết quả của ví dụ 1 để thấy sự khác biệt.
threadA: objID=obj1 – entering doStuff()
threadA: objID=obj1 – in doStuff() – local variable num=10
threadA: objID=obj1 – leaving doStuff()
threadB: objID=obj1 – entering doStuff()
threadB: objID=obj1 – in doStuff() – local variable num=18
threadB: objID=obj1 – leaving doStuff()
2.Các khối được đồng bộ(synchronized statement block)
Các khối được đồng bộ có thể được sử dụng khi toàn bộ phương thức không cần synchronized hoặc khi muốn nhận lock trên một đối tượng khác. Cách sử dụng nó như sau:
synchronized(obj){
// block code
}
Ví dụ:
class Client{
BankAccount account;
// …
public void updateTransaction(){
synchronized(account){       // (1) Khối đồng bộ
account.update();        // (2)
}
}
}
Ngoài ra, có thể sử dụng statement block để thay thế cho phương thức được synchronized như sau:
public synchronized void setPoint(int x, int y) {
this.x = x;
this.y = y;
}
Thay thế bằng statement block như sau:
public void setPoint(int x, int y) {
synchronized ( this ) {
this.x = x;
this.y = y;
}
}

Tìm hiểu Thread trong JAVA-Phần 3

Bài 3: Tìm Hiểu Một Số Phương Thức Hữu Dụng
1.Phương thức sleep()
Thread.sleep làm cho thread hiện hành đình chỉ thực hiện trong một thời gian quy định. Đây là một phương tiện hiệu quả làm cho thời gian xử lý đối với các thread khác của một ứng dụng hoặc các ứng dụng khác mà có thể được chạy trên một hệ thống máy tính. Phương thức sleep() cũng có thể được sử dụng cho pacing, như trong ví dụ sau đây, và chờ đợi một thread với nhiệm vụ được hiểu là có yêu cầu về thời gian, như với ví dụ SleepMessages ở bên dưới.
Có 2 overload của phương thức sleep() được cung cấp: một xác định thời gian sleep tính bằng mili giây và một xác định thời gian sleep bằng nano giây. Tuy nhiên, những kiểu thời gian này không đảm bảo chính xác, bởi chúng được giới hạn bởi những cơ sở nằm bên dưới hệ điều hành. Ngoài ra, thời gian sleep có thể được kết thúc bởi interrupt, mà chúng ta sẽ giới thiệu trong phần sau. Trong mọi trường hợp, bạn không thể giả định rằng việc gọi phương thức sleep sẽ đình chỉ thread về khoảng thời gian chính xác cho trước.
Ví dụ SleepMessages sử dụng phương thức sleep() để in message sau 4 giây:
public class SleepMessages {
    public static void main(String args[]) throws InterruptedException {
        String importantInfo[] = {
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "A kid will eat ivy too"
        };

        for (int i = 0; i < importantInfo.length; i++) {
            //Pause for 4 seconds
            Thread.sleep(4000);
            //Print a message
            System.out.println(importantInfo[i]);
        }
    }
}
Chú ý: phương thức main() ném một exception là InterruptedException. Đây là exception mà phương thức sleep() sẽ ném nếu một thread khác ngắt thread hiện hành trong khi sleep() đang kích hoạt. Nếu ứng dụng không định nghĩa một thread khác để làm gián đoạn(interrupt), thì không cần bận tâm tới InterruptedException.
2.Phương thức wait() và notify()
Cơ chế chiếm dụng đồng bộ hóa ngăn cho các tuyến đoạn chồng chéo nhau. Nhưng trong một số trường hợp ta cũng cần phải cung cấp một cách nào đó để các tuyến đoạn truyền tin với nhau. Java cung cấp cho người sử dụng các phương thức cho phép các tuyến đoạn không bị xử lý chồng chéo mà vẫn có thể trao đổi thông tin với nhau bằng cách sử dụng các phương thức wait() và notify(). Để thực hiện điều này phương thức wait() được định nghĩa để cho phép một tuyến đoạn đợi cho tới khi một điều kiện nào đó xảy ra. Phương thức notify() được định nghĩa để báo cho các tuyến đoạn biết sự kiện nào đó đã xảy ra. Các phương thức này được định nghĩa trong lớp Object và được thừa kế từ các lớp Object.
Ta tìm hiểu ví dụ sau :
public class WaitNotify extends Thread {
public static void main(String args[]) throws Exception{
Thread notificationThread = new WaitNotify();
notificationThread.start();
// Chờ tuyến đoạn cảnh báo kích hoạt sự kiện
synchronized (notificationThread){
notificationThread.wait();
}
// Báo cho người dùng biết phương thức wait đã hoàn thành
System.out.println (“The wait is over”);
}
public void run(){
System.out.println (“Hit enter to stop waiting thread”);
try {
System.in.read();
} catch (java.io.IOException ioe){
// no code req’d
}
// Notify any threads waiting on this thread
synchronized (this){
this.notifyAll();
}
}
}
Một số dạng của phương thức wait và notify
Tất cả các phương thức đều có trong lớp Object và hoạt động trên đối tượng hiện thời:
•  public final void wait(long timeout) throws InterruptedException
Tuyến  đoạn hiện thời chờ cho tới khi  được cảnh báo hoặc một khoảng thời gian timeout nhất định. Nếu timeout bằng 0 thì phương thức sẽ chỉ chờ cho tới khi có cảnh báo về sự kiện.
•  public final void notify()
Cảnh báo ít nhất một tuyến đoạn đang chờ một điều kiện nào đó thay đổi. Các tuyến đoạn phải chờ một điều kiện thay đổi trước khi có thể gọi phương thức wait nào đó.
•  public final void notifyAll()
Phương thức này cảnh báo tất cả các tuyến đoạn đang chờ một điều kiện thay đổi.Các tuyến đoạn đang chờ thường chờ một tuyến đoạn khác thay đổi điều kiện nào đó. Trong số các tuyến đoạn đã được cảnh báo, tuyến đoạn nào có độ ưu tiên cao nhất thì sẽ chạy trước tiên.
3.Một số phương thức về Interrupting Thread
  • Một Thread được xem là kết thúc khi phương thức run() của nó được trả về.
  • Không có cách nào để bắt buộc một Thread kết thúc. Tuy nhiên, phương thức interrupt() có thể được sử dụng để yêu cầu một Thread kết thúc.
  • Khi một Thread gọi phương thức interrupt(), trạng thái interrupted của Thread được thiết lập. Đó là môt flag biểu Boolean đại diện cho mỗi Thread. Thỉnh thoảng, mỗi Thread phải tự kiểm tra xem nó đã bị interrupted hay chưa. Một số phương thức interrupt của Thread:
    • void interrupt():
Gửi một request interrupt vào Thread. Trạng thái interrupted của Thread sẽ được thiết lập = true. Phương thức này đơn giãn set flag trong Thread đích để cho biết rằng nó đã bị interrupted và đã được return. Phương thức này có thể ném một ngoại lệ SecurityException, để cho biết rằng Thread yêu cầu interrupted không có quyền interrupt Thread khác.
Ví dụ:
1: public class SleepInterrupt extends Object implements Runnable {
2:     public void run() {
3:         try {
4:             System.out.println(
5:                     “in run() – about to sleep for 20 seconds”);
6:             Thread.sleep(20000);
7:             System.out.println(“in run() – woke up”);
8:         } catch ( InterruptedException x ) {
9:             System.out.println(
10:                     “in run() – interrupted while sleeping”);
11:             return;
12:         }
13:
14:         System.out.println(“in run() – doing stuff after nap”);
15:         System.out.println(“in run() – leaving normally”);
16:     }
17:
18:
19:     public static void main(String[] args) {
20:         SleepInterrupt si = new SleepInterrupt();
21:         Thread t = new Thread(si);
22:         t.start();
23:
24:         // Be sure that the new thread gets a chance to
25:         // run for a while.
26:         try { Thread.sleep(2000); }
27:         catch ( InterruptedException x ) { }
28:
29:         System.out.println(
30:                 “in main() – interrupting other thread”);
31:         t.interrupt();
32:         System.out.println(“in main() – leaving”);
33:     }
34: }
Kết quả:
in run() – about to sleep for 20 seconds
in main() – interrupting other thread
in main() – leaving
in run() – interrupted while sleeping
  • static boolean  interrupted():
Kiểm tra trạng thái interrupted của Thread hiện tại. Lưu ý đây là phương thức static. Phương thức này trả về true nếu Thread hiện tại đã bị interrupted và flag interrupted vẫn chưa được clear. Gọi Thread.interrupted() là một cách thứ 2 để trả về false cho đến khi Thread đã bị reinterrupted.
Ví dụ:
1: public class InterruptReset extends Object {
2:     public static void main(String[] args) {
3:         System.out.println(
4:             “Point X: Thread.interrupted()=” + Thread.interrupted());
5:         Thread.currentThread().interrupt();
6:         System.out.println(
7:             “Point Y: Thread.interrupted()=” + Thread.interrupted());
8:         System.out.println(
9:             “Point Z: Thread.interrupted()=” + Thread.interrupted());
10:     }
11: }
Kết quả:
Point X: Thread.interrupted()=false
Point Y: Thread.interrupted()=true
Point Z: Thread.interrupted()=false
Ban đầu, tại điểm X, trạng thái interrupted = false, sau đó Thread hiện tại bị interrupt tại dòng(5). Tại Y, trạng thái interrupted = true, và nó tự động reset lại = false bởi Thread.interrupted() (dòng 6-7 ). Cuối cùng, kiểm tra trạng thái tại Z và kết quả bằng false.
  • boolean isInterrupted():
Kiểm tra xem Thread này đã bị interrupt hay chưa. Gọi phương thức này không ảnh hưởng đến trạng thái interrupt của Thread này.

Tạo Thread Và Vòng Đời Thread

Bài 2: Tạo Thread Và Vòng Đời Thread
Định nghĩa và start một thread:
Một ứng dụng tạo một instance của Thread phải cung cấp code sẽ chạy trong thread đó. Có 2 cách để làm điều này:
  • Cung cấp một đối tượng Runnable: Giao diện Runnable định nghĩa một phương thức đơn, phương thức run(). Khi bạn implement giao diện Runnable, bạn phải cài đặt code trong phương thức run(). Đối tượng Runnable được gán cho Contructor của Thread, ví dụ HelloRunnable:
public class HelloRunnable implements Runnable {
public void run() {
System.out.println(“Hello from a thread!”);
}
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
}
  • Tạo một class extend từ Thread: Bản thân lớp thread đã implement giao diện Runnable, phương thức run() của nó không làm gì cả. Một ứng dụng tạo ra 1 lớp extend từ thread, phải cung cấp phần thực thi của phương thức run(), ví dụ:
public class HelloThread extends Thread {
public void run() {
System.out.println(“Hello from a thread!”);
}
public static void main(String args[]) {
(new HelloThread()).start();
}
}
Chú ý cả 2 ví dụ đều gọi Thread.start() để bắt đầu thread mới.
Vậy bạn nên sử dụng 2 trường hợp trên trong điều kiện nào. Ở trường hợp đầu tiên, sử dụng đối tượng Runnable, lớp implement giao diện này có thể kế thừa từ một lớp khác. Ở trường hợp thứ hai, lớp extend từ thread thì không thể kế thừa thêm lớp nào ngoài thread, bởi Java chỉ cho phép đơn thừa kế.
Lớp thread định nghĩa một số phương thức hữu ích cho việc quản lý Thread. Chúng bao gồm các phương thức tĩnh cung cấp thông tin hay tác động đến trạng thái của thread gọi các phương thức này. Các phương thức khác được gọi từ các thread khác liên quan đến việc quản lý thread và đối tượng Thread.
Trạng thái của Thread
Một Thread bao gồm 4 trạng thái sau:
  • New
  • Runnable
  • Blocked
  • Dead
Trạng thái New:
Khi tạo một Thread với toán tử new, thì Thread vẫn chưa được chạy. Điều này có nghĩa là nó ở trong trạng thái New. Khi một Thread ở trong trạng thái New, Thread vẫn chưa bắt đầu thực thi code ở bên trong nó.
Trạng thái Runnable:
Khi gọi phương thức start(), thì Thread rơi vào trạng thái Runnable. Khi ở trong trạng thái này, một Thread hoặc có thể đang chạy hoặc không, nhưng không có điều gì có thể ngăn cản nó thực thi nếu sheduler có thể sắp xếp nó. Nó phụ thuộc vào hệ điều hành gán thời gian thực thi cho nó. Và nó không phải là dead hay blocked.
Trạng thái Blocked:
Thread rơi vào trạng thái Blocked nếu một trong các action sau đây xảy ra:
  • Gọi phương thức sleep().
  • Thread gọi một operation mà nó đang bị blocking trên Input/Output.
  • Thread cố gắng giành lấy khóa(lock) trong khi khóa này đang được nắm giữ bởi một Thread khác.
  • Thread đang đợi một điều kiện nào đó để thực thi.
  • Một ai đó gọi phương thức suppend() của Thread. Tuy nhiên, phương thức này đã bị phản đối và không nên gọi nó trong code. Tóm lại, khi một Thread ở trong trạng thái blocked, scheduler sẽ bỏ qua nó và không cho nó thời gian CPU để xử lý, và nó không thể thực thi bất kỳ hoạt động nào.
Trạng thái Dead:
Một Thread rơi vào trạng thái Dead với một trong 2 lý do sau:
  • Thực thi xong phương thức run().
  • Một ngoại lệ chưa được bắt(uncaught) được phát sinh và kết thúc phương thức run().
Ngoài ra, có một cách khác có thể kill một Thread bằng cách gọi phương thức stop(). Tuy nhiên, phương thức này đã bị ngăn cấm và không nên sử dụng phương thức này trong code.
Quyền ưu tiên(Priority)
  • Mỗi Thread trong Java đều có một quyền ưu tiên(priority) giúp cho hệ điều hành xác định được thứ tự thực hiện của các Thread được thực thi.
  • Giới hạn của nó:  MIN_PRIORITY(có giá trị 1) và MAX_PRIORITY(có giá trị 10).
  • Thông thường, Thread có quyền ưu tiên cao hơn sẽ được ưu tiên xử lý. Theo mặc định,  mỗi một Thread được cho một quyền ưu tiên NORM_PRIORITY(có giá trị 5).
  • Hầu hết Java Platforms đều hỗ trợ bảng biểu làm việc(timeslicing). Nếu không có timeslicing, thì mỗi Thread trong tập hợp các Thread có quyền ưu tiên ngang nhau đều thực thi (trừ khi nó từ trạng thái runnable rơi vào trạng thái waiting hay timed waiting, hay bị ngắt bởi Thread có quyền thực thi cao hơn) trước khi các Thread khác có quyền ưu tiên ngang nhau nhận được cơ hội thực thi. Với Timeslicing, thậm chí nếu Thread chưa thực thi xong khi thời gian thực thi vượt quá giới hạn cho phép, thì processor sẽ đẩy Thread đó ra và đưa một Thread tiếp theo(có độ ưu tiên bằng Thread trước đó) vào để thực thi.
  • Lưu ý:Những constants(MAX_PRIORITY, MIN_PRIORITY) được khai báo trong lớp Thread. Và ta không tạo và sử dụng các đối tượng Thread để thực thi đồng thời mà phải thực thi giao diện Runnable.

Tìm hiểu Thread trong JAVA-Phần 1

Tìm hiểu Thread trong JAVA-Phần 1

Bài 1: Giới thiệu về Thread

Đồng bộ(Concurrency)
Máy tính ngày này cho phép ta sử dụng một lúc nhiều ứng dụng, chẳng hạn như bạn vừa nghe nhạc, vừa đánh văn bản word, vừa download nhạc…Hay thậm chí là một ứng dụng đơn cũng thực hiện nhiều task ở cùng một thời điểm. Ví dụ, trình soạn thảo văn bản word, nó luôn luôn sẵn sàng đáp ứng các sự kiện về keyboard và mouse, nó vừa phải reformat text và cập nhật lại màn hình. Các phần mềm làm những task như vậy gọi là phần mềm đồng bộ.
Java platform cũng được thiế kế hỗ trợ lập trình đồng bộ, với hỗ trợ về ngôn ngữ lập trình Java và các thư viện lớp Java. Từ phiên bản 5.0 trở đi, java platform cũng có các API đồng bộ high-level.
Processes và Thread
Trong lập trình đồng bộ, có 2 đơn vị thực thi cơ bản: tiến trình(process) và luồng(thread). Trong ngôn ngữ lập trình Java, lập trình đồng bộ chủ yếu liên quan đến thread. Tuy nhiên, các tiến trình cũng rất quan trọng.
Các hệ thống máy tính thông thường có nhiều tiến trình và thread hoạt động. Thậm chí điều này cũng đúng trong các hệ thống chỉ có một lõi(core) thực thi duy nhất, và vì thế chỉ có một thread thực sự thực hiện tại bất kỳ thời điểm. Thời gian xử lý cho một lõi duy nhất được chia sẻ giữa các tiến trình và thread thông qua một tính năng của hệ điều hành gọi là thời gian slicing.
Nó ngày càng phổ biến cho các hệ thống máy tính có nhiều bộ xử lý hay bộ vi xử lý với nhiều lõi thực hiện. Điều này giúp tăng cường khả năng để thực hiện đồng thời các tiến trình và thread của hệ thống – thậm chí có thể thực hiện đồng thời trên các hệ thống đơn giản, không có nhiều bộ xử lý lõi hoặc thực hiện.
Các tiến trình(Processes)
Một tiến trình có một môi trường thực thi khép kín. Thông thường, một tiến trình thường có một tập các tài nguyên run-time cơ bản độc quyền, và mỗi tiến trình có bộ nhớ không gian riêng của mình.
Các tiến trình thường được xem như các chương trình hay các ứng dụng. Tuy nhiên, những gì mà người dùng thấy như là một ứng dụng đơn gồm nhiều tiến trình cùng hoạt động. Để tạo thuận lợi trong việc giao tiếp giữa các tiến trình, hầu hết các hệ điều điều hành đều hỗ trợ các tài nguyên Inter Process Communication(IPC), như pipe hay socket. IPC không chỉ được sử dụng để giao tiến giữa các tiến trình trong cùng một hệ thống, mà còn giữa các tiến trình trên các hệ thống khác nhau.
Hầu hết thực thi của máy ảo Java chạy như là một tiến trình đơn. Một ứng dụng Java có thể tạo ra các tiến trình bổ xung bằng cách sử dụng đối tượng ProcessBuilder.
Thread
Đôi khi các thread còn được gọi là các tiến trình lightweight. Cả tiến trình và thread đều cung cấp một môi trường thực thi, nhưng tạo ra một thread mới yêu cầu tài nguyên ít hơn là tạo ra một tiến trình mới.
Các thread tồn tại trong một tiến trình- mỗi tiến trình có tối thiểu một thread. Các thread chia sẽ cùng một tài nguyên của tiến trình, bao gồm bộ nhớ và các file mở. Điều này làm cho giao tiếp hiệu quả nhưng lại tiềm ẩn bên trong nó các vấn đề về xử lý tranh chấp tài nguyên giữa các thread.
MultiThread
Các ngôn ngữ lập trình thông thường  không cho phép người lập trình thực hiện nhiều công việc cùng một lúc. Thay vào đó, cung cấp các điều khiển mà chỉ cho phép người lập trình thực hiện một công việc tại một thời điểm, đó là chỉ cho phép thực hiện công việc tiếp theo sau khi đã hoàn tất công việc trước đó. Java cho phép  người lập trình thưc hiện nhiều công việc thông qua API của nó. Người lập trình có thể tạo Thread thực thi, trong đó mỗi thread được phân chia như là một phần của chương trình và có thể thực thi đồng thời với các Thread khác, điều này được gọi là Multiple Thread.
Hầu hết các hệ điều hành ngày nay đều có nhiều hơn một thread chạy đồng thời tại cùng một thời điểm bên trong một tiến trình. Khi máy ảo Java(JavaVM) chạy trên các hệ điều hành đó, một tiến trình mới cũng được tạo ra. Bên trong tiến trình đó, có nhiều thread có thể được tạo ra.
Thông thường, bạn nghĩ các đoạn mã Java bắt đầu thực thi trong phương thức main() và tiến hành tại một đường dẫn thông qua chương trình. Đây chỉ là một ví dụ về thread đơn. Thread chính được sinh ra bởi JavaVM, mà bắt đầu thực thi với phương thức main, nó thực thi tất cả statement trong phương thức main(), và nó chết khi phương thức main() được hoàn tất.
Một thread thứ hai luôn luôn chạy trong JavaVM, đó là bộ dọn rác(garbage collection), nó dọn dẹp các đối tượng và khôi phục lại bộ nhớ. Vì vậy, khi bạn viết một đoạn mã java đơn giãn là System.out.println(“Xin chào”) thì nó cũng được chạy trên môi trường multithread, đó là main thread và garbage collention thread.
Khi một chương trình Java bào gồm một giao diện người dùng đồ họa(GUI), thì JavaVM tạo ra nhiều thread. Một trong các thread này phụ trách cung cấp các sự kiện GUI cho các phương thức trong chương trình, còn thread khác chịu trách nhiệm về vẽ cửa sổ GUI.
Ví dụ: ta có một chương trình dựa trên GUI, với một thread tính toán đang thực thi các công việc tính toán phức tạp và dài hạn, và trong khi điều này đang diễn ra, người dùng nhấn nút Stop. GUI event thread ngay sau đó sẽ gọi các sự kiện xử lý code tương ứng cho nút nhấn Stop, cho phép thread tính toán kết thúc. Nếu chương trình này chỉ có một thread, thì cả hai không thể thực thi cùng lúc, và sẽ gây ra tình trạng gián đoạn.
Thực thi multithread cũng là một đặc điểm thiết yếu của Java platform. Một ứng dụng đều có tối thiểu một thread-hay nhiều nếu bạn đếm các thread hệ thống làm những việc như quản lý bộ nhớ và xử lý tín hiệu. Tuy nhiên, nếu đứng trên quan điểm của người phát triển ứng dụng, bạn chỉ bắt đầu với một thread, và được gọi là main thread. Và thread này có khả năng tạo ra các thread bổ xung, và sẽ được đề cập trong những phần tiếp theo.
Thread Objects
Mỗi thread được liên kết với một thể hiện của lớp Thread. Có hai chiến lược cơ bản cho việc sử dụng các đối tượng Thread để tạo ra một ứng dụng đồng thời.
  • Điều khiển trực tiếp khi tạo ra thread và quản lý nó, đơn giãn là khởi tạo thread mỗi lúc ứng dụng cần bắt đầu một task không đồng bộ.
  • Quản lý thread trừu tượng từ tài nguyên của ứng dụng, đưa các task của ứng dụng vào một bộ thực thi(executor).

http://mobilesprogramming.wordpress.com/2010/01/15/tim-hi%E1%BB%83u-thread-trong-java-ph%E1%BA%A7n-1/

Process và Thread là gì?

Process và Thread là gì?

Định nghĩa:
Quá trình (process) là trạng thái tức thời của một chương trình đang chạy trên máy tính. Nó bao gồm bộ nhớ cần thiết để chạy chương trình (không gian địa chỉ của quá trình) và khả năng kiểm soát hiện trạng của bộ xử lý trong quá trình thực thi chương trình (tiến trình điều khiển của quá trình). Luồng (thread) tương tự như quá trình nhưng chỉ bao gồm tiến trình điều khiển. Nhiều luồng sử dụng không gian địa chỉ của một quá trình.
Quá trình và luồng có chung một mục đích: buộc máy tính phải làm nhiều việc hơn tại một thời điểm. Để làm điều đó, bộ xử lý (hay các bộ xử lý) phải chuyển đổi một cách trơn tru giữa các tác vụ, điều này đòi hỏi chương trình ứng dụng phải được thiết kế để chia sẻ tài nguyên máy tính.
Đó là lý do tại sao lập trình viên cần chia những gì chương trình phải làm thành quá trình và luồng.
Mỗi chương trình chạy trên một máy tính cần ít nhất là một quá trình. Quá trình đó bao gồm không gian địa chỉ (phần bộ nhớ máy tính mà ở đó chương trình chạy) và tiến trình điều khiển (cách thức để biết được phần nào của chương trình đang được bộ xử lý thực thi tại bất kỳ thời điểm nào). Nói cách khác, quá trình là một vùng làm việc và cách thức quản lý những gì chương trình đang thực hiện. Khi một số chương trình chạy cùng một thời điểm, mỗi chương trình sẽ có không gian địa chỉ và tiến trình điều khiển của riêng nó (xem sơ đồ).
Để phục vụ nhiều người dùng, một quá trình có thể cần phải phân nhánh, hay tạo bản sao của chính nó để tạo ra một quá trình con. Cũng giống như quá trình mẹ, quá trình con cũng có không gian địa chỉ và tiến trình điều khiển riêng. Tuy nhiên, thường thì khi quá trình mẹ chấm dứt, mọi quá trình con mà nó khởi động cũng sẽ tự động chấm dứt.
Hệ điều hành đa nhiệm như Unix hay Windows thực hiện việc chuyển đổi qua lại giữa các quá trình, lần lượt phân thời gian sử dụng CPU cho từng quá trình. Nếu máy tính có nhiều CPU, mỗi quá trình có thể được gán riêng cho một trong các CPU.
Điều này thích hợp với các chương trình đơn giản. Các ứng dụng phức tạp hiện nay như xử lý văn bản hay bảng tính có thể xem như là nhiều chương trình khác nhau với yêu cầu chuyển đổi qua lại và giao tiếp giữa các quá trình liên tục. Đây là một vấn đề vì phải mất thời gian để chuyển đổi giữa các quá trình.
CPU hiện đại có bộ quản lý bộ nhớ (memory management unit – MMU) để ngăn bất kỳ quá trình nào vi phạm không gian bộ nhớ của quá trình khác. Chuyển từ một quá trình này sang quá trình khác – được gọi là chuyển ngữ cảnh – có nghĩa là lập trình lại MMU để chỉ đến không gian địa chỉ khác cùng với việc lưu và phục hồi thông tin của một quá trình. Hệ điều hành chịu trách nhiệm quản lý chi tiết của việc chuyển ngữ cảnh nhưng nó cũng tiêu tốn thời gian của CPU. Do mỗi quá trình đều được cách ly với những quá trình khác, giao tiếp giữa các quá trình đòi hỏi phải có những chức năng đặc biệt. Tương tự việc chuyển ngữ cảnh, truyền thông giữa các quá trình cũng chiếm thời gian của bộ xử lý.
Tất cả thời gian trên sẽ cộng dồn lên khi nhiều chương trình chạy cùng lúc hay khi có nhiều người dùng mà mỗi người đều yêu cầu chạy nhiều quá trình cũng lúc. Càng nhiều quá trình chạy thì càng tốn nhiều thời gian của CPU và hệ điều hành để thực hiện công việc chuyển ngữ cảnh.
Nếu số quá trình đủ nhiều, máy chủ có thể phải dành toàn bộ thời gian để thực hiện việc chuyển đổi giữa các quá trình mà không thể thực sự xử lý được công việc nào.
Phân Luồng
Để tránh tình trạng trên, lập trình viên có thể dùng luồng (thread). Luồng cũng giống một quá trình con, ngoại trừ đặc điểm là mọi luồng kết hợp với một quá trình nào đó chia sẻ cùng không gian địa chỉ.
Ví dụ, nếu có nhiều người dùng cùng chương trình, lập trình viên có thể viết ứng dụng sao cho ứng với mỗi người dùng sẽ có một luồng mới được tạo ra.
Mỗi luồng có tiến trình kiểm soát riêng nhưng nó lại chia sẻ cùng không gian địa chỉ và hầu hết dữ liệu với tất cả luồng khác chạy trong cùng quá trình. Đối với từng người dùng có thể nói rằng chương trình dường như chỉ chạy cho một mình họ.
Ưu điểm là gì?
Thời gian chuyển đổi giữa các luồng ít hơn hẳn so với giữa các quá trình vì không cần phải chuyển đổi không gian địa chỉ. Ngoài ra, vì chúng chia sẻ không gian địa chỉ nên các luồng trong một quá trình có thể giao tiếp với nhau dễ dàng hơn nhiều.
Trên máy tính có nhiều bộ xử lý, chương trình dạng một quá trình đơn chỉ chạy trên một CPU, còn chương trình dạng luồng có thể chia các luồng cho tất cả các bộ xử lý. Vì thế, nếu bạn chuyển chương trình dạng luồng sang máy chủ nhiều bộ xử lý thì nó sẽ chạy nhanh hơn.
Khuyết điểm?
Chương trình dạng luồng khó viết và kiểm lỗi hơn. Không phải mọi thư viện lập trình đều được thiết kế để dùng với luồng và không phải mọi ứng dụng cũ đều có thể làm việc tốt với ứng dụng dạng luồng. Một vài công cụ lập trình cũng làm cho việc thiết kế và thử nghiệm mã luồng khó khăn hơn.
Lỗi liên quan đến luồng cũng khó phát hiện hơn. Các luồng trong một quá trình có thể bị chồng chéo dữ liệu với nhau. Hệ điều hành có thể hạn chế số luồng thực thi chẳng hạn đọc và ghi dữ liệu cùng lúc. Việc định thời cho các luồng khác nhau để tránh xung đột là rất khó khăn.
Tuy nhiên, khi các ứng dụng dùng chung phức tạp và máy chủ nhiều bộ xử lý ngày càng phổ biến thì luồng sẽ ngày càng được dùng nhiều để thực hiện đa xử lý.

Tham khảo JSP

JSP (viết tắt của tiếng Anh JavaServer Pages) còn được biết đến với một cái tên khác là Java Scripting Preprocessor - tạm dịch là "Bộ tiền xử lý văn lệnh Java" - là một công nghệ lập trình mã kịch bản phía máy chủ cho phép các nhà phát triển tạo ra các ứng dụng dựa trên Web đồng thời cho phép tận dụng các thành phần được phát triển bằng ngôn ngữ Java, tuân thủ theo đặc tả ngôn ngữ JavaBeans. Xét trên một vài khía cạnh thì JSP có thể so sánh với các ngôn ngữ scripting phía máy chủ khác như ASP và PHP. JSP giống như PHP ở khía cạnh cú pháp kiểu C của ngôn ngữ Java nhưng JSP lại có thể được thiết kế để có khả năng mở rộng thêm bằng cách dùng các thành phần như kiểu của ASP.

Ta có thể liệt kê các công nghệ phía server phổ biến là :
1. Common Gateway Interface (CGI)
2. Proprietary Web server APIs (ISAPI, NSAPI)
3. Server-side Java Scrípt(SSJS)
4. Personal Home Page (PHP)
5. Servlet
6. Active Server Pages(ASP)
7. JavaServer Page(JSP)

Trong thời gian đầu tiên của www, nguời ta thuờng xử dụng CGI (Common Gateway Interface) để xử lí trang động. CGI thông thuờng là những shell script hay perl (các bạn đã xử dụng UNIX sẽ quen thuộc với những loại script này). Khi có yêu cầu cho 1 trang động, web server sẽ chuyển yêu cầu này đến 1 script file nào đó và khởi động script file này. Nội dung của toàn bộ hay 1 phần của trang động đuợc tạo ra bởi script file. Kĩ thuật ban đầu này gặp nhiều khó khăn. Việc khởi động 1 script file cũng tuơng đuơng với chạy 1 process hay 1 ứng dụng, nó đòi hỏi khá nhiều tài nguyên (resource) của web server. Nếu có hàng ngàn yêu cầu 1 lúc thì web server sẽ đầu hàng chứ không thể nào khởi động hàng ngàn process cùng 1 lúc. Hơn nữa shell script hay perl không phải là những đối thủ lợi hại cho việc lập trình chưa nói đến việc bảo quản hay nâng cấp chúng rất khó khăn.

Cũng có nhiều kĩ thuật khác để xử lí trang động, Microsoft và Netscape đã hợp tác và đưa ra một cải tiến đáng kể là sử dụng các file thư viện liên kết động (DLL ), cho phép chia sẻ mã lệnh giữa các tiến trình. Đây chính là các kỹ thuật ISAPI và NSAPI.

Đen đủi thay, các kỹ thuật dựa trên DLL không phải là đã hoàn thiện. Chúng vẫn còn một số vấn đề:
- Khi các thư viện nền tảng được gọi, nếu muốn thoát các ứng dụng này, ta phải tắt chương trình triệu gọi (Web server) và khởi động lại máy tính.
- Các thư viện cần được đặt trong các tuyến trình bảo vệ, tức là chúng cần phải được cảnh giác về cách sử dụng các biến chung hoặc các biến tĩnh.
- Nếu chương trình triệu gọi gây ra lỗi truy cập, nó có thể dẫn đến tình trạng server bị treo.
- Và cuối cùng: khi đã được dịch ra các file DLL, công việc gỡ lỗi cũng như bảo trì mã lệnh trở nên vất vả hơn bao giờ hết.

Kỹ thuật Web mới nhất của Microsoft, kết hợp HTML, các đoạn Script, các thành phần xử lý phía server trong cùng một file, được gọi là ASP (Active Server Pages), với phiên bản mới nhất hiện nay là ASP.Net. ASP được triệu gọi bởi một thư viện liên kết động gắn với các Web server của Microsoft. Về bản chất, ta có thể coi ASP như là một ngôn ngữ thông dịch vậy. Một trang ASP có thể sử dụng HTML, JScript và VBScript. Qua các đoạn mã nhúng này, ASP có thể truy cập đến các thành phần phía server. Các thành phần này có thể được viết trên bất kỳ ngôn ngữ nào hỗ trợ các thành phần COM của Microsoft. Và đây chính là sức mạnh của ASP: nó có thể làm được bất kỳ cái gì mà máy chủ có thể làm được với các thành phần COM. Sau khi được thi hành, ASP sẽ sản sinh ra một trang Web có khuôn dạng HTML và trả nó về cho Web server.

Một bất lợi lớn đối với ASP là nó chỉ có thể hoạt động trên các họ Web server của Microsoft (bao gồm PWS trên Win9x hay IIS trên WinNT/2000/XP). Các nhà phát triển đang hướng đến những môi trường khác như Unix/Linux, nhưng kết quả thì còn phải đợi thêm một thời gian nữa

Servlet & JSP được phát triển dự trên kiến trúc MVC
o Các trang JSP đóng vai trò hiển thị các kết quả của người dùng (thành phần View)
o Servlet sẽ đóng vai trò là các Controller, điều khiển các yêu cầu được gửi lên từ các trang JSP
o Các class khác: bean, process thực hiện các chức năng nghiệp vụ: đọc, lưu dữ liệu sẽ thuộc vào thành phần Model

Servlet API được phát triển dựa trên những điểm mạnh của Java platform nhằm giải quyết những vấn đề của CGI và trình chủ server API. Nó là một API đơn giản hỗ trợ tất cả các Web server và thậm chí các ứng dụng máy chủ dùng để kiểm tra và quản lý các công việc trên server (load-balancing). Nó giải quyết vấn đề thực thi bằng việc thực hiện tất cả các yêu cầu như những thread trong một process, hoặc trên một hệ thống load-balancing sẽ là mỗi process trên một server trong kết chùm cluster. Các servlet dễ dàng chia sẽ tài nguyên.

Nó xen lẫn giữa HTML và Java nguồn, cũng có nghĩa là nó đòi hỏi nguời tạo trang web phải khá thành thạo trong lập trình bằng Java. Những nguời tạo trang web, thuờng đuợc xem là những nguời có đầu óc mỹ thuật hơn là kĩ thuật. Tìm kiếm 1 nguời vừa giỏi cả mỹ thuật lẫn kĩ thuật là 1 chuyện khó. Những nguời tạo web thường làm quen dễ dàng với những HTML tag hơn là với Java nguồn. JSP ra đời sau đó để giải quyết dứt điểm vấn đề này. Ngày nay Servlet chỉ thuờng xử dụng trong những truờng hợp đặc biết như tạo ra hình vẽ, đồ thị động...

Công nghệ JSP cho phép người ta nhúng mã Java và một số hành động xử lý đã được định trước (pre-defined actions) vào trong nội dung tĩnh của trang.

Cú pháp của JSP cho thêm các thẻ XML mới, gọi là JSP actions - hành động JSP. Những "hành động JSP" này được dùng để khởi động chức năng sẵn có, là những chức năng đã được xây dựng trước. Cộng thêm vào đó, công nghệ còn cho phép chúng ta tạo ra các thư viện thẻ JSP (JSP tag libraries), là những cái đóng vai trò vào việc mở rộng các thẻ HTML hay XML tiêu chuẩn. Thư viện thẻ (Tag libraries) là phương pháp mở rộng khả năng của một máy chủ web trong khi những mở rộng đó không phụ thuộc vào hệ nền (về cả cấu trúc máy cũng như hệ điều hành được dùng).

Trước khi hiển thị ra trình duyệt, tập tin JSP phải được biên dịch thành Servlet, dùng bộ biên dịch JSP (JSP compiler). Bộ biên dịch JSP có thể tạo servlet thành mã nguồn Java trước, rồi biên dịch mã nguồn ra tập tin .class dùng bộ biên dịch Java, hoặc có thể trực tiếp tạo mã byte code cho servlet từ trang JSP .Về mặt kiến trúc mà nói, ta có thể xem JSP là một trừu tượng ở mức độ cao của servlet, một phần mở rộng thực thi của Servlet 2.1 API.

JSP chạy thông qua drive ODBC:JDBC. Cổng mặc định của nó là 8000 (còn cổng mặc định của IIS là 80). Tuy nhiên bạn cũng có thể cho nó chạy độc lập với IIS hoặc là tích hợp với IIS. Một ứng dụng có thể có cả ASP và JSP.

Trong Servlet/JSP, bảo mật được cải tiến theo nhiều cách. Trước hết, bạn hiếm khi thực thi lệnh trên shell với dữ liệu cung cấp bởi người dùng khi Java API đã cung cấp truy cập đến tất cả những hàm sử dụng thông dụng. Bạn có thể sử dụng JavaMail để đọc và gởi mail, Java DataBase Connect (JDBC) để truy cập các database, lớp File và những lớp quan hệ để truy cập hệ thống file, RMI, CORBA, Enterprise Java Beans (EJB) để truy cập các hệ thống kế thừa. Việc xử lý các ngoại lệ của Java cũng làm cho một servlet chắc chắn hơn các trình chủ API C/C++ ...

Khi bạn tiếp cận với JSP, bạn sẽ thấy nó không giống các ngôn ngữ khác cùng loại. Cực nhanh, độc lập nền tảng, tính tương thích quy mô rất cao với sự trợ giúp từ các hàm xây dựng sẵn cho các công việc xử lý phân tán, tương thích với tất cả các web server quan trọng nhất và MIỄN PHÍ cho tất cả mọi người, cho tất cả các công việc kể cả thương mại.

Giới thiệu về JSP và Servlet (phần 5)

Tất cả các servlet thuộc về một ngữ cảnh (context) servlet. Trong phiên bản 1.0 và 2.0 của Servlet API, tất cả các servlet trên một host thuộc về một context, nhưng với phiên bản 2.1 của API, context trở nên hữu dụng hơn và có thể được xem như xuất phát điểm của một khái niệm Ứng dụng. Các phiên bản tương lai của API sẽ làm cho điều này thậm chí trở nên rõ ràng hơn.
Các thuộc tính ServletContext
Tất cả các servlet thuộc về một ngữ cảnh (context) servlet. Trong phiên bản 1.0 và 2.0 của Servlet API, tất cả các servlet trên một host thuộc về một context, nhưng với phiên bản 2.1 của API, context trở nên hữu dụng hơn và có thể được xem như xuất phát điểm của một khái niệm Ứng dụng. Các phiên bản tương lai của API sẽ làm cho điều này thậm chí trở nên rõ ràng hơn.
Nhiều servlet engine bổ sung Servlet API 2.1 giúp cho bạn nhóm một tập các servlet vào trong một context và hỗ trợ nhiều context trong cùng một host. ServletContext trong API 2.1 có thể phụ thuộc vào trạng thái của các servlet của nó và sự phân biệt các tài nguyên (resource) và thuộc tính (attribute) có sẵn cho các servlet trong context. Ở đây, chúng ta sẽ chỉ xem xét các attribute của ServletContext có thể được dùng để chia sẻ thông tin giữa một nhóm các servlet như thế nào.
Có ba phương thức ServletContext tương ứng với các attribute: getAttribute, setAttribute và removeAttribute. Thêm vào đó, servlet engine có thể cung cấp nhiều cách để cấu hình servlet context với các giá trị attribute khởi tạo. Việc quản lý này giống như một sự chấp nhận thêm vào các đối số khởi tạo servlet cho việc cấu hình thông tin sử dụng bởi một nhóm các servlet, cho thực thể của định danh database mà chúng ta đã đề cập ở phần trước, một bảng kiểu (style sheet) URL cho một ứng dụng, tên của một mail server …
Một server nhận một tham chiếu đến đối tượng ServletContext của nó thông qua đối tượng ServletConfig. HttpServlet thật sự cung cấp một phương thức thuận lợi (thông qua siêu lớp của nó GenericServlet) mang tên getServletContext làm cho nó thật sự dễ dàng


ServletContext context = getServletContext();
String styleSheet = context.getParameter(“stylesheet”);
If(styleSheet != null) {
//Xác định một style sheet mới cho ứng dụng
Context.setAttribute(“stylesheet”, styleSheet);
}


Đoạn mã ở trên có thể là một phần của một ứng dụng cấu hình servlet, xử lý request từ một HTML FORM có một style sheet xác định cho ứng dụng. Tất cả các servlet trong ứng dụng tạo ra HTML có thể sử dụng attribute style sheet như sau:


ServletContext context = getServletContext();
String styleSheet = context.getAttribute(“stylesheet”);
out.println(“”);
out.println(“”);


Các thuộc tính và tài nguyên yêu cầu (Request attributes and resources)
Phiên bản 2.1 của API thêm vào hai kỹ thuật để chia sẽ dữ liệu giữa các servlet: request attibute và resource.
Các phương thức getAttribute, getAttributeNames và setAttribute được thêm vào cho lớp HttpServletRequest (hoặc chính xác hơn là cho siêu lớp ServletRequest). Chúng được xác định để sử dụng với RequestDispatcher, một đối tượng được sử dụng để chuyển tiếp (forward) một request từ một servlet này đến một servlet khác và để chứa một luồng xuất từ một servlet trong luồng xuất từ servlet chính.
Các phương thức getResourcev và getResourceAsStream của lớp ServletContext cho phép bạn truy cập đến những tài nguyên bên ngoài, chẳng hạn như một file cấu hình ứng dụng. Bạn có thể đã thân thuộc với những phương thức chia sẻ cùng một tên giống như những phương thức trong lớp ClassLoader. Tuy nhiên, những phương thức của lớp ServletContext có thể cung cấp cách truy cập đến những tài nguyên không thật sự là file. Một tài nguyên có thể được lưu trữ trong một database, có sẵn thông qua một LDAP server, trong trường hợp này tài nguyên là mọi thứ mà servlet engine quyết định hỗ trợ.
Servlet engine cung cấp một lựa chọn cấu hình context để bạn xác định được gốc của cơ sở tài nguyên, đó là một đường dẫn thư mục, một HTTP URL, một JDBC URL …
Các ví dụ về cách sử dụng những phương thức có thể là một chủ đề trong một article tương lai. Cho đến sau khi bạn có thể đọc chúng trong Servlet 2.1

Giới thiệu về JSP và Servlet (phần 3)

Giới thiệu về JSP và Servlet (phần 3)
Phần này giới thiệu về Request và Response, hai thành phần quan trọng trong Servlet
Các đối tượng request và response
Phương thức doGet có hai tham số HTTPServletRequest và HTTPServletResponse. Hai đối tượng này cho phép chúng ta truy cập đầy đủ tất cả các thông tin về yêu cầu và cho phép chúng ta gởi luồng xuất cho client như là trả lời cho yêu cầu.
Với CGI bạn đọc các biến môi trường và stdin để nhận thông tin về yêu cầu, tuy nhiên việc đặt tên các biến môi trường có thể khác nhau giữa các CGI, và một vài biến không được cung cấp bởi tất cả các Web server.
Đối tượng HTTPServletRequest cũng cung cấp thông tin giống như biến môi trường của CGI theo một hướng chuẩn. Nó cũng cung cấp những phương thức để mở ra các tham số HTTP từ chuỗi truy vấn hoặc thân của yêu cầu phụ thuộc vào kiểu yêu cầu (GET hay POST). Là một nhà phát triển servlet, bạn truy cập các tham số theo cùng một cách cho cả hai kiểu yêu cầu. Những phương thức khác cho bạn truy cập đến tất cả những header của yêu cầu và giúp bạn phân tích các header ngày và cookie.
Thay vì viết trả lời như stdout giống như bạn làm với CGI, bạn nhận một đối tượng OutputStream hoặc PrintWriter từ HTTPServletResponse. OutputStream được dành cho dữ liệu nhị phân như các ảnh GIF hoặc JPEG, và PrintWriter dành cho việc xuất văn bản text. Bạn cũng có thể ấn định tất cả các header trả lời và mã trạng thái mà không phải phụ thuộc vào các cấu hình cụ thể của Web server CGI nào. Điều này làm cho servlet của bạn dễ dàng cài đặt.
Hãy bổ sung vào thân của phương thức doGet và xem chúng ta sử dụng những phương thức này như thế nào. Chúng ta có thể đọc hầu hết các thông tin chúng ta có được từ HTTPServletRequest (lưu những phương thức này vào ví dụ kế tiếp) và gởi những giá trị này để trả lời cho yêu cầu

protected void doGet(HttpServletRequest request, HttpServletResponse response)                                                                          throws ServletException, IOException {
response.setContentType(“text/html”);
PrintWriter out = response.getWriter();
// in ra HTML header
out.println(“”);
out.println(“Request info”);
out.println(“”);
// in ra than HTML
out.println(“

Request info

”);
out.println(“getCharacterEncoding: ” +    request.getCharacterEncoding());
out.println(“getContentLength: ” + request.getContentLength());
out.println(“getContentType: ” + request.getContentType());
out.println(“getProtocol: ” + request.getProtocol());
out.println(“getRemoteAddr: ” + request.getRemoteAddr());
out.println(“getRemoteHost: ” + request.getRemoteHost());
out.println(“getScheme: ” + request.getScheme());
out.println(“getServerName: ” + request.getServerName());
out.println(“getServerPort: ” + request.getServerPort());
out.println(“getAuthType: ” + request.getAuthType());
out.println(“getMethod: ” + request.getMethod());
out.println(“getPathInfo: ” + request.getPathInfo());
out.println(“getPathTranslated: ” + request.getPathTranslated());
out.println(“getQueryString: ” + request.getQueryString());
out.println(“getRemoteUser: ” + request.getRemoteUser());
out.println(“getRequestURI: ” + request.getRequestURI());
out.println(“getServletPath: ” + request.getServletPath());
out.println();
out.println(“Parameters:”);
Enumeration paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String name = (String) paramNames.nextElement();
String[] values = request.getParameterValues(name);
out.println(” ” + name + “:”);

for (int i = 0; i < values.length; i++) {
out.println(” ” + values[i]);
}
}

out.println();
out.println(“Request headers:”);
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = (String) headerNames.nextElement();
String value = request.getHeader(name);
out.println(” ” + name + ” : ” + value);
}

out.println();
out.println(“Cookies:”);
Cookie[] cookies = request.getCookies();
for (int i = 0; i < cookies.length; i++) {
String name = cookies[i].getName();
String value = cookies[i].getValue();
out.println(” ” + name + ” : ” + value);
}

// in ra footer HTML
out.println(“
”); out.close(); } Phương thức doGet ở trên sử dụng hầu hết các phương thức trong HTTPServletRequest nhằm cung cấp thông tin về yêu cầu. Bạn có thể đọc chúng trong tài liệu servlet API, vì thế ở đây chúng ta chỉ tìm kiếm những gì đáng chú ý nhất. getParameterNames và getParameterValues giúp bạn truy cập những tham số HTTP nếu servlet được yêu cầu với phương thức GET hay post. getParameterValues trả về một dãy String bởi vì một tham số có thể có nhiều giá trị. Ví dụ nếu bạn yêu cầu servlet với một chuỗi URL http://localhost:8080/servlet/ReqInfoServlet?foo=bar&foo=baz thì bạn sẽ thấy tham số foo có hai giá trị bar và baz. Điều này đúng nếu như bạn sử dụng chung một tên định danh cho nhiều thành phần của form HTML và sử dụng phương thức POST trong thẻ ACTION. Nếu như bạn chắc chắn rằng một tham số HTTP chỉ có thể có một giá trị thì bạn có thể sử dụng phương thức getParameter thay vì getParameterValues. Nó trả về một chuỗi String và nếu có nhiều giá trị nó sẽ trả về giá trị đầu tiên nhận được từ yêu cầu. Bạn truy cập đến tất cả header của yêu cầu HTTP với các phương thức getHeaderNames và getHeader. getHeader trả về một chuỗi String của header. Nếu như bạn biết header có chứa giá trị ngày hay một giá trị integer bạn có thể nhận được sự giúp đỡ chuyển đổi header sang một định dạng tương thích. getDateHeader trả về một ngày là số mili giây bắt đầu từ 00:00:00 GMT . ngày 1/1/1970. Đây là sự thay thế số chuẩn cho thời gian trong Java (tương tự như Unix). Bạn có thể sử dụng nó để khởi dựng một đối tượng ngày. getIntHeader trả về header giá trị kiểu int. getCookie sẽ phân tích Cookie và trả về tất cả cookie trong một dãy các đối tượng Cookie. Để thêm một cookie vào trả lời, HTTPServletResponse cung cấp phương thức addCookie đặt một đối tượng Cookie như là một đối số. Nếu như bạn biên dịch ReqInfoServlet và cài đặt nó trên servlet engine của bạn và gọi nó thông qua trình duyệt với một URL giống như và mọi thứ đều đúng theo sau http://localhost:8080/servlet/ReqInfoServlet/foo/bar?fee=baz thì trong trình duyệt sẽ hiển thị Request info getCharacterEncoding: getContentLength: -1 getContentType: null getProtocol: HTTP/1.0 getRemoteAddr: 127.0.0.1 getRemoteHost: localhost getScheme: http getServerName: thangnc getServerPort: 8080 getAuthType: null getMethod: GET getPathInfo: /foo/bar getPathTranslated: D:\PROGRA~1\jsdk2.1\httproot\servlet\ReqInfoServlet\foo\bar getQueryString: fee=baz getRemoteUser: null getRequestURI: /servlet/ReqInfoServlet/foo/bar getServletPath: /servlet/ReqInfoServlet Parameters: fee: baz Request headers: Connection : Keep-Alive User-Agent : Mozilla/4.5 [en] (WinNT; I) Host : thangnc Accept : image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* Accept-Encoding : gzip Accept-Language : en Accept-Charset : iso-8859-1,*,utf-8 Cookie : TOMCATID=TO04695278486734222MC1010AT Cookies: TOMCATID : TO04695278486734222MC1010AT Nếu bạn muốn servlet này xử lý cả yêu cầu GET và POST? Việc bổ sung mặc định doGet và doPost trả về một thông điệp thông báo rằng phương thức này chưa được bổ sung. Vì thế chúng ta chỉ phải cung cấp một bổ sung mới của doGet. Để có thể xử lý một yêu cầu POST theo cùng một cách, chúng ta chỉ cần gọi doGet từ doPost protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); }

http://blackdiamond89.wordpress.com/2009/09/06/gi%E1%BB%9Bi-thi%E1%BB%87u-v%E1%BB%81-jsp-va-servlet-ph%E1%BA%A7n-3/

Giới thiệu về JSP và Servlet

Giới thiệu về JSP và Servlet (Phần 1)

Java servlets được tạo ra nhằm mục đích giải quyết nhiều vấn đề trong CGI và những trình chủ server API. Trong article này, chúng tôi mô tả một cách tổng quát về kiến trúc servlet và những gì bạn cần để phát triển ứng dụng web với servlet.
Mở đầu
Java servlets được tạo ra nhằm mục đích giải quyết nhiều vấn đề trong CGI và những trình chủ server API. Trong article này, chúng tôi mô tả một cách tổng quát về kiến trúc servlet và những gì bạn cần để phát triển ứng dụng web với servlet. Chúng tôi sử dụng những đoạn mã ví dụ khác nhau để chỉ cho bạn thấy cách sử dụng servlet API như thế nào và so sánh nó với CGI cũng như những trình chủ server API. Tôi giả sử bạn đã thân thuộc với HTTP và CGI hoặc những trình chủ server API như NSAPI hoặc ISAPI. Tôi cũng cho rằng bạn đã thân thuộc với ngôn ngữ lập trình Java hoặc một vài ngôn ngữ lập trình hướng đối tượng khác, ví dụ C++. Thậm chí nếu bạn không phải là một lập trình viên Java, thì bạn cũng có thể thấy được lợi ích của servlet sau khi bạn đọc article này.

Thời kỳ tối tăm

Quay về những năm 90 ban đầu, khi mà web chỉ mới bắt đầu chiếm lĩnh. Common Gateway Interface (CGI) đã định nghĩa cho phép các Web server xử lý những gì mà người dùng nhập vào và làm việc với nội dung động. Các chương trình CGI có thể được phát triển trên bất kỳ một ngôn ngữ lập trình hay ngôn ngữ script nào, tuy nhiên Perl là ngôn ngữ được sử dụng phổ biến nhất. Thật sự tất cả các Web server đều hỗ trợ CGI và nhiều module Perl có sẵn như những phần mềm miễn phí hay phần mềm chia sẻ để xử lý hầu hết những nhiệm vụ.
Tuy nhiên CGI không phải không có trở ngại. Việc thực thi và tính hiệu quả là những vấn đề lớn khi một xử lý mới được tạo ra cho mỗi yêu cầu, làm cho tài nguyên server nhanh chóng bị bận. Việc chia sẻ tài nguyên cũng như các kết nối dữ liệu giữa các script hay việc gọi cùng một script nhiều lần, dẫn đến lặp lại nhiều lần những công việc thực thi.
Bảo mật là sự lo lắng lớn với CGI. Hầu hết các script Perl dùng shell để thực thi các lệnh hệ điều hành với dữ liệu cung cấp bởi người dùng, những thực thể để gởi mail, tìm kiếm một file, hay chỉ là những lệnh hệ điều hành tổng quát. Việc sử dụng shell theo cách này sẽ mở ra nhiều cơ hội cho một hacker sửa lại một script nhằm xóa đi tất cả các file trên một server, gởi file mật khẩu của server đến một account bí mật hoặc làm những hành động không được phép khác mà một máy chủ không mong đợi.
Những nhà cung cấp Web server đã định nghĩa những API để giải quyết một vài vấn đề này, cụ thể là ISAPI của Microsoft và NSAPI của Netscape. Nhưng một ứng dụng được viết trên những trình chủ API này lại kết hợp với một nhà cung cấp Web server cụ thể. Nếu bạn cần di chuyển một ứng dụng sang một Web server khác của một nhà cung cấp phần mềm khác, bạn phải bắt đầu lại từ đầu. Vấn đề khác với hướng đi này là đáng tin cậy. Các API thông thường hỗ trợ mã thực thi C/C++ trong xử lý của các Web server. Nếu một ứng dụng bị lỗi, ví dụ như con trỏ bị lỗi hoặc chia cho 0, thì toàn bộ Web server sẽ bị sụp đổ cùng với nó.

Giải pháp với Servlet
Servlet API được phát triển dựa trên những điểm mạnh của Java platform nhằm giải quyết vấn đề của CGI và trình chủ server API. Nó là một API đơn giản hỗ trợ tất cả các Web server và thậm chí các ứng dụng máy chủ dùng để kiểm tra và quản lý các công việc trên server (load -balancing). Nó giải quyết vấn đề thực thi bằng việc thực hiện tất cả các yêu cầu như những thread trong một xử lý, hoặc trên một hệ thống load-balancing sẽ là mỗi xử lý trên một server trong kết chùm cluster. Các servlet dễ dàng chia sẽ tài nguyên.
Trong định nghĩa servlet, bảo mật được cải tiến theo nhiều cách. Trước hết, bạn hiếm khi thực thi lệnh trên shell với dữ liệu cung cấp bởi người dùng khi Java API đã cung cấp truy cập đến tất cả những hàm sử dụng thông dụng. Bạn có thể sử dụng JavaMail để đọc và gởi mail, Java DataBase Connect (JDBC) để truy cập các database, lớp File và những lớp quan hệ để truy cập hệ thống file, RMI, CORBA, Enterprise Java Beans (EJB) để truy cập các hệ thống kế thừa…
Mô hình bảo mật Java tạo cho nó khả năng bổ sung các điều khiển truy cập tốt hơn, cho thực thể chỉ cho phép truy cập đến một phần được xác định tốt hơn trong hệ thống file. Việc xử lý các ngoại lệ của Java cũng làm cho một servlet chắc chắn hơn các trình chủ API C/C++ – một phép chia cho 0 được thông báo như một lỗi thay vì làm sụp đổ cả một hệ thống Web server.