Tải bản đầy đủ (.pdf) (36 trang)

Phát triển với Java thời gian thực, Phần 1: Khai thác các đặc tính độc nhất của Java thời gian thực pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (3.69 MB, 36 trang )

Phát triển với Java thời gian thực, Phần 1: Khai thác các đặc tính độc nhất
của Java thời gian thực
Tận dụng hiệu năng Java thời gian thực trong ứng dụng của bạn
Sean C. Foley, Phát triển phần mềm, IBM
Tóm tắt: Bộ Java thời gian thực (Real-time Java™) kết hợp dễ dàng việc lập
trình bằng ngôn ngữ Java theo hiệu năng do ứng dụng yêu cầu mà phải phù hợp
với các ràng buộc thời gian thực. Các phần mở rộng của ngôn ngữ Java đưa ra các
đặc tính về môi trường thời gian thực mà đang thiếu trong môi trường thời gian
chạy Java truyền thống. Bài này, bài đầu tiên trong loạt bài ba phần, mô tả một số
đặc tính này và giải thích cách bạn có thể áp dụng chúng để đạt được hiệu năng
thời gian thực trong các ứng dụng của chính mình.
Java thời gian thực là một bộ các tăng cường cho ngôn ngữ Java, cung cấp cho các
ứng dụng một mức hiệu năng thời gian thực, vượt trội hiệu năng của công nghệ
Java chuẩn. Hiệu năng thời gian thực khác với hiệu năng thông lượng truyền
thống, là một thước đo điển hình của tổng số các chỉ thị, tác vụ, hoặc công việc có
thể được thực hiện trong khoảng thời gian ấn định. Hiệu năng thời gian thực tập
trung vào thời gian mà một ứng dụng yêu cầu để đáp ứng các kích thích bên ngoài
mà không vượt quá các ràng buộc thời gian cho trước. Trong trường hợp của các
hệ thống thời gian thực cứng (hard real-time), các ràng buộc như vậy không bao
giờ được vượt quá; các hệ thống thời gian thực mềm (soft real-time) có một dung
sai cao hơn đối với các vi phạm. Hiệu năng thời gian thực đòi hỏi chính ứng dụng
phải giành được quyền điều khiển của bộ xử lý sao cho nó có thể trả lời các kích
thích, và trong khi trả lời các tác nhân kích thích đó thì bộ mã của ứng dụng không
bị khóa do thực hiện các quy trình tương tranh trong máy ảo đó. Java thời gian
thực đưa ra độ đáp ứng mà trước đây chưa được thoả mãn trong các ứng dụng
Java.
Một máy ảo Java (JVM) thời gian thực có thể tận dụng các dịch vụ hệ điều hành
thời gian thực (RTOS) để cung cấp các khả năng thời gian thực cứng, hoặc nó có
thể chạy trên nhiều hệ điều hành thông thường đối với các áp dụng có các ràng
buộc thời gian thực mềm dẻo hơn. Một số công nghệ sử dụng trong Java thời gian
thực trở nên “miễn phí” khi bạn chuyển sang sử dụng máy ảo Java thời gian thực.


Nhưng để khai thác một số đặc tính của Java thời gian thực, cần phải có một số
thay đổi về ứng dụng. Các đặc tính này là trọng tâm của bài viết này.
Các quy trình con phải bị ràng buộc
Một JVM phục vụ một ứng dụng cho trước bằng cách thực hiện công việc mà ứng
dụng đó chỉ điều khiển theo cách lỏng. Một vài quy trình thời gian chạy con làm
việc trong JVM, gồm:
 Gom rác: Đây là công việc để phục hồi lại các khối nhớ thời gian chạy
(run-time memory) mà ứng dụng đã loại bỏ. Việc gom rác có thể làm chậm
việc thực thi ứng dụng trong một khoảng thời gian.
 Nạp lớp: Quy trình này — gọi như vậy vì các ứng dụng Java được nạp ở
mức chi tiết của các lớp, liên quan đến việc nạp các cấu trúc ứng dụng —
các chỉ thị, và các tài nguyên khác từ hệ thống tệp hoặc mạng. Trong Java
chuẩn, ứng dụng nạp từng lớp khi nó được tham chiếu lần đầu (nạp chậm).
 Biên dịch động đúng thời (JIT dynamic compilation): Nhiều máy ảo sử
dụng việc biên dịch động của các phương thức từ ngôn ngữ máy của Java
(Java bytecode) sang các chỉ thị máy riêng khi ứng dụng đang chạy. Mặc dù
việc này cải thiện được hiệu năng, hoạt động biên dịch tự nó có thể gây ra
sự trì hoãn tạm thời, khóa việc chạy mã ứng dụng.
 Lập lịch: Trong Java chuẩn, cho phép mức điều khiển tối thiểu để ứng
dụng ra lệnh cả việc lập lịch việc chạy các xử lí (threads) của chính mình
lẫn lập lịch của ứng dụng tương quan với các ứng dụng khác đang chạy trên
cùng hệ điều hành.
Tất cả các quy trình con này có thể gây trở ngại đến khả năng phản hồi các tác
nhân kích thích bên ngoài của một ứng dụng, vì chúng có thể làm chậm việc thực
thi bộ mã ứng dụng. Thí dụ một chuỗi chỉ thị hẳn có thể được lên lịch thực hiện để
trả lời một tín hiệu từ mạng, hệ thống radar, bàn phím, hoặc bất kỳ thiết bị nào
khác. Một ứng dụng thời gian thực có một khoảng thời gian tối thiểu chấp nhận
được trong đó một quy trình không liên quan đến, chẳng hạn như cho phép gom
rác làm chậm việc thực hiện chuỗi chỉ thị trả lời.
Java thời gian thực đưa ra các công nghệ đa dạng được thiết kế để giảm can thiệp

đến ứng dụng khỏi các quy trình con ẩn này. Các công nghệ “miễn phí” này xuất
hiện khi bạn chuyển sang JVM thời gian thực bao gồm việc gom rác đặc biệt có
hạn chế khoảng thời gian và tác động của các gián đoạn đối với việc thu gom, tải
lớp đặc biệt mà cho phép hiệu năng được tối ưu hoá vào lúc khởi động, thay vì
việc tối ưu hoá bị chậm, khoá và đồng bộ hoá đặc biệt, và lập lịch xử lí ưu tiên đặc
biệt với việc tránh bị đảo ngược quyền ưu tiên. Tuy nhiên, đòi hỏi một số thay đổi
cho ứng dụng — cụ thể là khai thác các đặc tính do Đặc tả Thời gian Thực cho
Java (RTSJ) đưa ra.
RTSJ đảm bảo một API có nhiều đặc tính thời gian thực trong các JVM. Một số
các đặc tính này có tính bắt buộc khi thực hiện đặc tả, số khác thì tuỳ ý. Đặc tả bao
hàm các lĩnh vực chung về:
 Lập lịch thời gian thực
 Quản lý nhớ nâng cao
 Các bộ định thời gian phân giải cao
 Xử lý sự kiện không đồng bộ
 Ngắt không đồng bộ các xử lí


Các xử lí thời gian thực
RTSJ định nghĩa javax.realtime.RealtimeThread — là một lớp con của lớp chuẩn
java.lang.Thread. Trên chính nó, RealtimeThread tạo ra một số đặc tính tiên tiến
của đặc tả. Thí dụ các xử lí thời gian thực là chủ thể của bộ lập lịch xử lí thời gian
thực. Bộ lập lịch đảm bảo một phạm vi duy nhất các quyền ưu tiên lập lịch và có
thể thực hiện chính sách lập lịch thời gian thực vào trước - ra trước (đảm bảo các
xử lí có quyền ưu tiên cao nhất được thực hiện không bị gián đoạn), cùng với việc
kế thừa quyền ưu tiên (một thuật toán tránh các xử lí quyền ưu tiên thấp hơn giữ
vô hạn một khoá mà một xử lí có quyền ưu tiên cao hơn đang yêu cầu và được
chạy không bị cản trở — tình huống này được xem như đảo quyền ưu tiên).
Bạn có thể xây dựng nên một cách rõ ràng các cá thể của RealtimeThread (xử lí
thời gian thực) trong mã của bạn. Nhưng cũng có thể thay đổi ứng dụng của bạn

theo một cách tối thiểu để xử lí thời gian thực, nên tránh được sự cố gắng phát
triển đáng kể và các chi phí liên quan. Thể hiện sau đây là các ví dụ khác nhau về
các cách để cho phép tạo xử lí thời gian thực ít can thiệp nhất và minh bạch nhất.
(Bạn có thể tải về mã nguồn cho toàn bộ các thí dụ trong bài viết.) Các kỹ thuật
này cho phép một ứng dụng khai thác các xử lí thời gian thực với sự cố gắng tối
thiểu và cho phép ứng dụng giữ được sự tương thích với các máy ảo chuẩn.
Chỉ định kiểu xử lí theo quyền ưu tiên
Liệt kê 1 trình bày một khối mã gán một xử lí thời gian thực hoặc xử lí thông
thường với giá trị ưu tiên. Nếu nó đang chạy trên một máy ảo thời gian thực, một
số xử lí có thể là xử lí thời gian thực.

Liệt kê 1. Gán lớp xử lí theo quyền ưu tiên


import javax.realtime.PriorityScheduler;
import javax.realtime.RealtimeThread;
import javax.realtime.Scheduler;

public class ThreadLogic implements Runnable {
static void startThread(int priority) {
Thread thread = ThreadAssigner.assignThread(
priority, new ThreadLogic());
thread.start();
}

public void run() {
System.out.println("Running " + Thread.currentThread());
}
}


class ThreadAssigner {
static Thread assignThread(int priority, Runnable runnable) {
Thread thread = null;
if(priority <= Thread.MAX_PRIORITY) {
thread = new Thread(runnable);
} else {
try {
thread = RTThreadAssigner.assignRTThread(priority, runnable);
} catch(LinkageError e) {}
if(thread == null) {
priority = Thread.MAX_PRIORITY;
thread = new Thread(runnable);
}
}
thread.setPriority(priority);
return thread;
}
}

class RTThreadAssigner {
static Thread assignRTThread(int priority, Runnable runnable) {
Scheduler defScheduler = Scheduler.getDefaultScheduler();
PriorityScheduler scheduler = (PriorityScheduler) defScheduler;
if(priority >= scheduler.getMinPriority()) {
return new RealtimeThread(
null, null, null, null, null, runnable);
}
return null;
}
}



Mã trong Liệt kê 1 phải được biên dịch bằng các lớp RTSJ. Vào thời gian chạy,
nếu không tìm thấy các lớp thời gian thực, mã sẽ nắm bắt được LinkageError bị
máy ảo loại bỏ và tạo các đối tượng xử lí Java thông thường thay cho các xử lí thời
gian thực. Việc này cho phép mã chạy trên bất kỳ máy ảo nào, dù có thời gian thực
hay không.
Trong Liệt kê 1, phương thức cung cấp các đối tượng RealtimeThread được tách
riêng thành một lớp của chính nó. Với cách này, phương thức không được xác
thực cho đến khi lớp được nạp vào, điều được làm khi phương thức
assignRTThread được truy cập đầu tiên. Khi lớp được nạp, bộ kiểm tra ngôn ngữ
máy của máy ảo thời gian thực cố gắng kiểm tra lại lớp RealtimeThread có phải là
một lớp con của lớp Thread, hay không nó thông báo thất bại với một lỗi
NoClassDefFoundError nếu không tìm ra các lớp thời gian thực.
Chỉ định các xử lí nhờ phản chiếu
Liệt kê 2 trình bày một kỹ thuật thay thế có cùng hiệu quả như Liệt kê 1. Nó khởi
động bằng một giá trị ưu tiên để xác định kiểu xử lí mong muốn, tạo ra hoặc là
một xử lí thời gian thực hoặc một xử lí thông thường dựa trên lớp tên. Mã phản
chiếu trông chờ vào sự tồn tại của một hàm kiến thiết trong lớp mà lấy một cá thể
của java.lang.Runnable làm đối số cuối cùng và chuyển giá trị rỗng cho tất cả đối
số khác.

Liệt kê 2. Sử dụng phản chiếu để chỉ định các xử lí

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ThreadLogic implements Runnable {
static void startThread(int priority) {
Thread thread = ThreadAssigner.assignThread(

priority, new ThreadLogic());
thread.start();
}

public void run() {
System.out.println("Running " + Thread.currentThread());
}
}

class ThreadAssigner {
static Thread assignThread(int priority, Runnable runnable) {
Thread thread = null;
try {
thread = assignThread(priority <= Thread.MAX_PRIORITY, runnable);
} catch(InvocationTargetException e) {
} catch(IllegalAccessException e) {
} catch(InstantiationException e) {
} catch(ClassNotFoundException e) {
}
if(thread == null) {
thread = new Thread(runnable);
priority = Math.min(priority, Thread.MAX_PRIORITY);
}
thread.setPriority(priority);
return thread;
}

static Thread assignThread(boolean regular, Runnable runnable)
throws InvocationTargetException, IllegalAccessException,
InstantiationException, ClassNotFoundException {

Thread thread = assignThread(
regular ? "java.lang.Thread" :
"javax.realtime.RealtimeThread", runnable);
return thread;
}

static Thread assignThread(String className, Runnable runnable)
throws InvocationTargetException, IllegalAccessException,
InstantiationException, ClassNotFoundException {
Class clazz = Class.forName(className);
Constructor selectedConstructor = null;
Constructor constructors[] = clazz.getConstructors();
top:
for(Constructor constructor : constructors) {
Class parameterTypes[] =
constructor.getParameterTypes();
int parameterTypesLength = parameterTypes.length;
if(parameterTypesLength == 0) {
continue;
}
Class lastParameter =
parameterTypes[parameterTypesLength - 1];
if(lastParameter.equals(Runnable.class)) {
for(Class parameter : parameterTypes) {
if(parameter.isPrimitive()) {
continue top;
}
}
if(selectedConstructor == null ||
selectedConstructor.getParameterTypes().length

> parameterTypesLength) {
selectedConstructor = constructor;
}
}
}
if(selectedConstructor == null) {
throw new InstantiationException(
"no compatible constructor");
}
Class parameterTypes[] =
selectedConstructor.getParameterTypes();
int parameterTypesLength = parameterTypes.length;
Object arguments[] = new Object[parameterTypesLength];
arguments[parameterTypesLength - 1] = runnable;
return (Thread) selectedConstructor.newInstance(arguments);
}
}

Bộ mã trong Liệt kê 2 không cần biên dịch với các lớp thời gian thực trên
classpath (một biến môi trường để JVM và Java tìm ra các thư viện lớp), do các xử
lí thời gian thực được tạo ra bằng cách sử dụng phản chiếu Java.
Chỉ định kiểu xử lí bằng sự kế thừa lớp
Ví dụ tiếp theo minh họa cách có thể xử lí thời gian thực của việc thay đổi sự kế
thừa của một lớp cho trước. Bạn có thể tạo ra hai phiên bản của một lớp xử lí cho
trước, một phiên bản là sự nhận thức được javax.realtime.RealtimeThread và
phiên bản kia không có. Lựa chọn của bạn về cái này hay cái kia phụ thuộc vào
JVM đang ẩn. Bạn có thể cho phép một trong hai cái chỉ đơn giản bằng cách bao
gồm tệp lớp tương ứng theo sắp xếp của bạn. Với lựa chọn nào, thì mã cũng tương
đối đơn giản và tránh được bất kỳ việc xử lý ngoại lệ nào, không giống như các ví
dụ trước đây. Tuy nhiên, khi bạn phân bổ ứng dụng, bạn phải bao gồm 1 trong 2

lựa chọn lớp, tuỳ thuộc vào máy ảo được liên kết nào sẽ chạy ứng dụng.
Mã trong Liệt kê 3 tạo ra các xử lí Java thông thường theo một cách chuẩn:

Liệt kê 3. Sử dụng kế thừa lớp để chỉ định các xử lí


import javax.realtime.PriorityScheduler;
import javax.realtime.RealtimeThread;
import javax.realtime.Scheduler;

public class ThreadLogic implements Runnable {
static void startThread(int priority) {
ThreadContainerBase base = new ThreadContainer(priority, new
ThreadLogic());
Thread thread = base.thread;
thread.start();
}

public void run() {
System.out.println("Running " + Thread.currentThread());
}
}

class ThreadContainer extends ThreadContainerBase {
ThreadContainer(int priority, Runnable runnable) {
super(new Thread(runnable));
if(priority > Thread.MAX_PRIORITY) {
priority = Thread.MAX_PRIORITY;
}
thread.setPriority(priority);

}
}

class ThreadContainerBase {
final Thread thread;

ThreadContainerBase(Thread thread) {
this.thread = thread;
}
}


Để kích hoạt các xử lí thời gian thực, bạn có thể thay đổi mã ThreadContainer như
trình bày trong Liệt kê 4:

Liệt kê 4. Một lớp thùng chứa xử lí khác để dùng thời gian thực

class ThreadContainer extends ThreadContainerBase {
ThreadContainer(int priority, Runnable runnable) {
super(assignRTThread(priority, runnable));
thread.setPriority(priority);
}

static Thread assignRTThread(int priority, Runnable runnable) {
Scheduler defScheduler = Scheduler.getDefaultScheduler();
PriorityScheduler scheduler = (PriorityScheduler) defScheduler;
if(priority >= scheduler.getMinPriority()) {
return new RealtimeThread(
null, null, null, null, null, runnable);
}

return new Thread(runnable);
}
}

Bạn có thể gộp vào tệp lớp ThreadContainer vừa được biên dịch này trong ứng
dụng của bạn chứ không phải tệp cũ khi chạy nó bằng một JVM thời gian thực.


Các vùng nhớ tách biệt
Phổ biến cho tất cả các JVM, gồm cả các JVM thời gian thực, là đống rác được
thu gom (garbage-collected heap). JVM phục hồi lại bộ nhớ từ đống qua việc gom
rác. Các JVM thời gian thực có các thuật toán thu gom rác được thiết kế riêng để
tránh hoặc giảm thiểu can thiệp vào ứng dụng đang chạy.
RTSJ đưa ra khái niệm về một ngữ cảnh cấp phát cho mỗi xử lí, và nó đưa vào các
vùng nhớ bổ sung. Khi một vùng nhớ dùng làm ngữ cảnh cấp phát cho một xử lí,
tất cả các đối tượng được tạo ra bởi xử lí đó được phân bổ từ khu vực đó. RTSJ
quy định các vùng nhớ được tách riêng phụ sau:
 Vùng nhớ đống đơn.
 Một vùng nhớ đống đơn bất tử (singleton immortal heap memory area), tức
bộ nhớ không bao giờ được sử dụng lại. Việc khởi tạo xử lí một lớp sử
dụng vùng này làm ngữ cảnh cấp phát khi chạy bộ khởi tạo tĩnh. Mặc dù bộ
nhớ bất tử không đòi hỏi sự chú ý từ bộ gom rác, việc sử dụng nó không bị
hạn chế, vì bộ nhớ không thể phục hồi được.
 Các vùng nhớ được khoanh vùng (scopes). Các vùng không đòi hỏi sự hoạt
động từ việc gom rác, và bộ nhớ của chúng có thể được phục hồi — lập tức
toàn bộ — để sử dụng lại. Các đối tượng được phân bổ trong một vùng
được hoàn chỉnh và dọn sạch, giải phóng bộ nhớ được phân bổ của chúng
để sử dụng lại, khi máy ảo đã xác định rằng vùng đó không còn là vùng ngữ
cảnh cấp phát cho bất kỳ xử lí sống nào nữa.
 Các vùng nhớ vật lý được định danh theo kiểu hoặc theo địa chỉ. Bạn có thể

thiết kế từng vùng nhớ vật lý để sử dụng lại như một khu vực được khoanh
vùng, hoặc để sử dụng đơn lẻ như một vùng bất tử. Các vùng nhớ như vậy
có thể cung cấp việc truy cập bộ nhớ các đặc tính riêng hoặc từ các thiết bị
riêng, chẳng hạn như bộ nhớ cực nhanh (flash memory) hoặc bộ nhớ dùng
chung (shared memory).
Vùng đưa ra các hạn chế áp đặt về các tham chiếu đối tượng. Khi một khối nhớ
được khoanh vùng được giải phóng và các đối tượng bên trong được thu dọn,
không một đối tượng nào với một quy chiếu đến khối nhớ được giải phóng có thể
tồn tại, mà sẽ đưa đến kết quả là một dấu quy chiếu lơ lửng (dangling pointer)
(một quy chiếu không định vị vào một địa chỉ nào). Việc này được thực hiện một
phần bởi sự áp đặt của các qui tắc gán. Các nguyên tắc này ra lệnh rằng các đối
tượng được phân bổ từ các vùng nhớ chưa được khoanh vùng không thể trỏ đến
các đối tượng được khoanh vùng. Điều này đảm bảo rằng khi các đối tượng được
khoanh vùng được giải phóng, các đối tượng từ các vùng nhớ khác không bị bỏ lại
với các tham chiếu đến các đối tượng không tồn tại.
Hình 1 minh hoạ các vùng nhớ và các qui tắc gán này:

Hình 1. Các vùng nhớ và các qui tắc gán đối với các tham chiếu đối tượng

Các qui tắc gán cho phép các đối tượng trong một vùng trỏ đến vùng khác. Tuy
nhiên, điều này có nghĩa là phải có một chuỗi bị áp đặt của việc làm sạch vùng đối
với mỗi xử lí, một chuỗi được duy trì bởi ngăn xếp (stack) trong mỗi xử lí. Ngăn
xếp này cũng bao gồm các tham chiếu đến các vùng nhớ khác mà đã được nhập
vào ngoài các vùng. Bất cứ khi nào một vùng nhớ trở thành ngữ cảnh cấp phát đối
với một xử lí, nó đều được đặt lên đỉnh ngăn xếp vùng của xử lí. Các qui tắc gán
ra lệnh rằng các đối tượng trong các vùng cao hơn trên vùng nhớ có thể quy chiếu
đến đối tượng trong các vùng thấp hơn trên ngăn xếp, vì các vùng trên đỉnh được
dọn sạch đầu tiên. Các tham chiếu từ các vùng thấp hơn đến các vùng cao hơn bị
cấm.
Thứ tự của các vùng trên vùng nhớ cũng được phối hợp với thứ tự của các vùng

trên vùng nhớ của các xử lí khác. Khi một vùng đã được đặt trên vùng nhớ của bất
kỳ xử lí nào, vùng gần nhất bên dưới nó trên ngăn xếp được coi là cha (hoặc cha
được coi là vùng sơ khởi đơn lẻ (solitary primordial scope)), nếu không còn vùng
nào khác trên vùng nhớ. Trong khi vùng đó duy trì được trên ngăn xếp đó, nó có
thể được đặt lên vùng nhớ của bất kỳ xử lí nào khác chỉ khi cha duy trì được sự
nhất quán, có nghĩa nó là vùng cao nhất trên ngăn xếp của xử lí khác. Nói một
cách khác, một vùng khi sử dụng chỉ có duy nhất một cha đơn lẻ. Điều này đảm
bảo rằng khi các vùng được giải phóng, việc thu dọn xảy ra theo một thứ tự dãy
giống như vậy mà không để ý đến xử lí nào thực hiện việc thu dọn của mỗi vùng,
và các qui tắc gán giữ được sự nhất quán qua tất cả các xử lí.
Cách khai thác các vùng nhớ tách riêng
Bạn có thể sử dụng một vùng nhớ riêng bằng cách quy định vùng này là vùng nhớ
ban đầu đối với một xử lí để chạy vào (khi đối tượng xử lí được kiến thiết), hoặc
bằng cách nhập vào vùng một cách rõ ràng, với điều kiện nó với một đối tượng
Runnable sẽ được thực hiện bằng vùng làm vùng mặc định.
Phải đặc biệt cân nhắc khi bạn sử dụng các vùng nhớ khác nhau, vì chúng mang
theo những phức tạp và rủi ro có thể xảy ra. Bạn phải chọn ra kích thước và số
lượng các vùng. Nếu các vùng đang được sử dụng, bạn phải cẩn thận khi thiết kế
sắp xếp thứ tự của các ngăn xếp vùng của các xử lí, và phải duy trì nhận thức về
các qui tắc gán.


Các tuỳ chọn để lập lịch mã nhạy thời gian
Khi bạn sử dụng các vùng nhớ heap khác, bạn có thể chọn dùng
javax.realtime.NoHeapRealtimeThread (NHRT - xử lí thời gian thực không có
đống), một lớp con của javax.realtime.RealtimeThread, cho phép các xử lí mà
được đảm bảo chạy không bị nhiễu từ bộ gom rác. Chúng có thể chạy mà không bị
nhiễu vì chúng bị hạn chế truy cập bất kỳ đối tượng nào được phân bổ từ đống.
Bất kỳ cố gắng vi phạm nào đến hạn chế truy cập này đều làm cho một
javax.realtime.MemoryAccessError (lỗi truy cập bộ nhớ java thời gian thực) bị

loại bỏ.
Một tuỳ chọn lập lịch khác là bộ xử lý sự kiện không đồng bộ (asynchronous event
handler), mà bạn có thể sử dụng để lập lịch bộ mã sẽ được thực hiện để trả lời các
sự kiện không đồng bộ hoặc theo chu kỳ. (Các sự kiện có thể là theo chu kỳ nếu
chúng được khởi động bởi một bộ định thời.) Việc này cho phép bạn từ bỏ nhu cầu
lập lịch các xử lí một cách rõ ràng đối với các sự kiện như vậy. Thay vào đó, máy
ảo duy trì một vùng đệm (pool) các xử lí được chia sẻ và gửi đi để chạy mã của
các bộ xử lý sự kiện không đồng bộ bất cứ khi nào sự kiện xảy ra. Việc này có thể
làm đơn giản hóa các ứng dụng thời gian thực, giải phóng bạn khỏi việc quản lý
các xử lí và vùng nhớ.
Sơ đồ lớp trong Hình 2 trình bày các tuỳ chọn sẵn có dùng vào việc lập lịch mã:

Hình 2. Các tuỳ chọn minh hoạ sơ đồ lớp để lập lịch mã

Hình 3 hiển thị cách các bộ xử lý sự kiện không đồng bộ được gửi đi:

Hình 3. Cách các bộ xử lý sự kiện không đồng bộ được gửi đi

Nói chung, có thể là có lợi về tính khả chuyển và tính modun nếu tách riêng mã
đáp ứng sự kiện từ bộ mã có thể kích hoạt và gửi đi bộ xử lý. Khi bộ mã được gói
trong một cài đặt của java.lang.Runnable, thì một số tuỳ chọn là có thể để gửi đi
bộ mã đó. Bạn có thể chọn xây dựng nên một xử lí để khai thác bộ mã, hoặc sử
dụng các bộ xử lý sự kiện không đồng bộ mà tận dụng các vùng đệm của các xử lí
để thực hiện bộ mã theo yêu cầu, hoặc sử dụng các kết hợp của hai cái trên.
Bảng 1 trình bày một phân tích tổng quát về các đặc tính của các lựa chọn khả dĩ
khác nhau:

Bảng 1. So sánh các phương thức gửi đi bộ mã trong Java thời gian thực

Chia

sẻ các
xử lí
để
thực
hiện
bộ mã

Có th

được
gửi đi
theo
đ
ịnh kỳ
Có th

chạy
trong
b
ộ nhớ
đống


thể
chạy
trong
bộ
nhớ
bất tử


Có thể
chạy
trong b

nhớ
được
khoanh
vùng
Có th

được
chỉ
định
một
thời
hạn
chót
Sẽ
chạy

không
bị
nhiễu
t
ừ việc
gom
rác
Thread thông thường Không

Không Có Có Không Không


Không
RealtimeThread Không

Có Có Có Có Có Không
NoHeapRealtimeThread Không

Có Không

Có Có Có Có
AsyncEventHandler Có
Có, khi
được
kèm
theo
một bộ
định
thời
định kỳ

Có Có Có Có Không
BoundAsyncEventHandler

Không

Có, khi
được
kèm
theo
một bộ

định
thời
đ
ịnh kỳ
Có Có Có Có Không
Không-đống
AsyncEventHandler

Có, khi
được
kèm
theo
một bộ
định
thời
đ
ịnh kỳ
Không

Có Có Có Có
Không-đống
BoundAsyncEventHandler

Không

Có, khi
được
kèm
theo
một bộ

định
thời
đ
ịnh kỳ
Không

Có Có Có Có
Vài vấn đề về thiết kế duy nhất đối với Java thời gian thực gây ảnh hưởng khi bạn
đang cân nhắc sử dụng các tuỳ chọn lập lịch và vùng nhớ. Việc lập trình đối với
các môi trường thời gian thực nói chung là một nhiệm vụ thách thức hơn lập trình
các ứng dụng truyền thống đơn giản, và Java thời gian thực đưa ra các thách thức
của chính nó. Bảng 2 liệt kê một số phức tạp mà có thể nảy sinh khi sử dụng vùng
nhớ bổ sung, các NHRT, và các đặc tính thời gian thực khác:

Bảng 2. Một số phức tạp và khó khăn không lường trước của xử lí và vùng
nhớ thời gian thực
Xem xét Chi tiết
Bộ nhớ phân bổ đến một vùng
nhớ
Mỗi vùng nhớ được tạo ra bởi một ứng dụng
được phân bổ với một kích thước yêu cầu. Chọn
một kích thước quá lớn là cách sử dụng bộ nhớ
không hiệu quả, nhưng việc chọn một kích thước
quá nhỏ có thể làm
ứng dụng dễ bị tổn hại đối với
OutOfMemoryError (lỗi hết bộ nhớ). Trong khi
phát triển, thậm chí khi một ứng dụng không
thay
đổi, các thư viện ẩn có thể thay đổi. Việc này có
thể tạo ra việc sử dụng bộ nhớ bổ sung không

mong muốn, làm cho giới hạn vùng nhớ bị vượt
quá. .
Các cân nhắc về định thời đối
với các vùng được chia sẻ
Một vùng nhớ được khoanh vùng được chia sẻ
bởi một số xử lí có thể xuất hiện để có đủ kích
thước vì nó chờ được thu dọn khi không có xử lí
nào sử dụng nó. Tuy nhiên, với các thay đổi tinh
vi trong việc định thời của các xử lí bằng cách sử
dụng vùng, có thể không bao giờ có lúc vùng đó
không được sử dụng khi ngữ cảnh cấp phát cho
bất kỳ xử lí nào. Việc này t
ạo ra khả năng bất ngờ
là nó sẽ không bao giờ được thu dọn, gây ra một
lỗi OutOfMemoryError.
Các tranh ch
ấp về khóa chặn tạm thời giữa các xử
lí có thể xảy ra khi chia sẻ các vùng được khoanh
vùng được chia sẻ nhập vào và được thu dọn.
Các ngoại lệ thời gian chạy
IllegalAssignmentError,
MemoryAccessError, và
IllegalThreadStateException
Các ngoại lệ này có th
ể có kết quả nếu không chú
ý đ
ầy đủ đến thiết kế mã. Trên thực tế, các thay
đổi tinh vi trong hành vi và định thời chương
trình có thể làm cho chúng xuất hiện ra một cách
đột ngột. Một số ví dụ:

 Một đối tượng từ đống mà thông thường
không sẵn có để dùng cho một NHRT có
thể trở nên sẵn có vì các thay đổi trong
việc định thời và đồng bộ hoá giữa các xử
lí.
 Một lỗi IllegalAssignmentError (lỗi chỉ
định không hợp lệ) có thể được đưa vào
khi không biết một đối tượng được phân
bổ từ vùng nhớ nào, hoặc nơi nào mà một
vùng riêng được định vị trên ngăn xếp
vùng.
 IllegalThreadStateException được loại bỏ
khi bộ mã nhập vào các các vùng nhớ
được khoanh vùng đư
ợc chạy bởi các xử lí
thông thường.
 Bộ mã tạo ra sự sử dụng phổ biến các
trường tĩnh hay phương tiện khác để nhớ
nhanh dữ liệu là không an toàn đối với các
vùng vì các qui tắc gán, mà có thể gây ra
một lỗi IllegalAssignmentError.
Khởi tạo lớp
Bất kỳ kiểu xử lí thời gian thực hoặc thông
thường nào đều có thể khởi tạo một lớp, bao gồm
cả một NHRT, có thể gây ra bất một lỗi không
mong đợi MemoryAccessError.
Việc hoàn tất của các đối tượng
với phương thức finalize
Xử lí cuối cùng để thoát ra một vùng được sử
dụng để hoàn thành tất cả các đối tượng bên

trong:
 Nếu các phương thức finalize t
ạo ra các xử
lí, các vùng không thể thu dọn được như
mong đợi.
 Việc hoàn tất cũng có thể đưa vào các
khoá chết. Trước khi hoàn tất vùng nhớ,
xử lí đang hoàn tất có thể đã thu được các
khoá. Tranh chấp đối với các khoá chặn
này từ các xử lí khác, và cũng là các khoá
sẽ có được trong khi hoàn t
ất, có thể xảy ra
và có thể tạo ra các khoá chết.
Các cản trở NHRT không mong
đợi
Các NHRT, mặc dù đư
ợc đảm bảo để chạy không
có nhiễu trực tiếp từ việc gom bộ nhớ rác, có thể
chia sẻ các khoá với các kiểu xử lí khác nhau có
thể có trước bằng việc gom bộ nhớ rác. Nếu
NHRT b
ị ngăn cản khi đang cố gắng giành một
khoá như vậy trong khi xử lí đang s
ở hữu khoá đó
bị làm trở ngại do việc gom bộ nhớ rác, thì
NHRT này c
ũng bị cản trở gián tiếp do việc gom
bộ nhớ rác.



Một thí dụ tổng hợp
Thí dụ tiếp theo bao hàm một số đặc tính thời gian thực đã được mô tả. Để bắt
đầu, Liệt kê 5 trình bày hai lớp mô tả một tác nhân tạo ra (producer) dữ liệu sự
kiện và một tác nhân tiêu thụ (consumer). Cả hai lớp là các cài đặt của Runnable
(có thể chạy được) để chúng có thể dễ dàng thực thi bởi bất kỳ đối tượng
Schedulable (có thể lập lịch) nào cho trước.

Liệt kê 5. Các lớp tác nhân tạo ra và tác nhân tiêu thụ đối với các đối tượng
sự kiện

class Producer implements Runnable {
volatile int eventIdentifier;
final Thread listener;

Producer(Thread listener) {
this.listener = listener;
}

public void run() {
LinkedList<Integer> events = getEvents();
synchronized(listener) {
listener.notify();
events.add(++eventIdentifier); //autoboxing creates an Integer object here
}
}

static LinkedList<Integer> getEvents() {
ScopedMemory memoryArea = (ScopedMemory)
RealtimeThread.getCurrentMemoryArea();
LinkedList<Integer> events =

(LinkedList<Integer>) memoryArea.getPortal();
if(events == null) {
synchronized(memoryArea) {
if(events == null) {
events = new LinkedList<Integer>();
memoryArea.setPortal(events);
}
}
}
return events;
}
}

class Consumer implements Runnable {
boolean setConsuming = true;
volatile boolean isConsuming;

public void run() {
Thread currentThread = Thread.currentThread();
isConsuming = true;
try {
LinkedList<Integer> events = Producer.getEvents();
int lastEventConsumed = 0;
synchronized(currentThread) {
while(setConsuming) {
while(lastEventConsumed < events.size()) {
System.out.print(events.get(lastEventConsumed++) + " ");
}
currentThread.wait();
}

×