Mẫu Thiết kế và Tác Động Đến Hiệu Năng
Mẫu thiết kế là phương pháp hệ thống hóa kỹ thuật lập trình, giúp giao tiếp chuyên nghiệp hơn giữa các kỹ sư. Ví dụ khi đề cập đến mô-đun I/O sử dụng mẫu trang trí (decorator), chúng ta dễ dàng hình dung cấu trúc mã nguồn. Tuy nhiên đa phần mẫu thiết kế không trực tiếp cải thiện hiệu năng mà chỉ tổ chức lại code. Bài viết tập trung vào các mẫu có ảnh hưởng thực tế đến performance: proxy, singleton, flyweight và prototype.
Phát Hiện Vấn Đề Hiệu Năng Trong Proxy Động
Spring sử dụng proxy động qua CGLIB để tăng cường bytecode. Trong dự án phức tạp với nhiều aspect (quyền truy cập, logging), hiệu năng có thể bị ảnh hưởng. Dưới đây là cách sử dụng Arthas để xác định nguyên nhân:
@Component
public class CoreService {
public void execute() {
System.out.println("Processing task");
}
}
@Aspect
@Component
public class PerformanceAspect {
@Pointcut("execution(* com.example.service.CoreService.execute(..))")
public void serviceMethod() {}
@Before("serviceMethod()")
public void logStart() {
System.out.println("Starting execution");
try {
Thread.sleep(950); // Giả lập xử lý chậm
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Khi truy cập endpoint /monitor, kết quả trả về dạng:
class com.example.service.CoreService$$EnhancerByCGLIB$$d8a3b1c2 | 958
Dùng Arthas để phân tích:
trace com.example.service.CoreService execute --skipJDKMethod false
Kết quả hiển thị rõ ràng thời gian xử lý lớn nhất nằm ở lớp proxy do aspect gây ra.
So Sánh Hiệu Năng Proxy Động
Java hỗ trợ hai cơ chế proxy chính:
- JDK Proxy: Yêu cầu interface, sử dụng
InvocationHandler - CGLIB: Proxy trực tiếp lớp, dùng
MethodInterceptor
Kết quả benchmark trên Java 17:
Benchmark Mode Cnt Score Error Units
ProxyPerformanceTest.cglib thrpt 10 81250.3 ± 982.1 ops/ms
ProxyPerformanceTest.jdk thrpt 10 89100.7 ± 435.6 ops/ms
CGLIB không vượt trội như đồn đại, thậm chí chậm hơn JDK proxy trong thực thi. Xét về tốc độ khởi tạo:
Benchmark Mode Cnt Score Error Units
ProxyInitializationTest.cglib thrpt 10 7420.1 ± 156.3 ops/ms
ProxyInitializationTest.jdk thrpt 10 16050.8 ± 210.5 ops/ms
Spring chọn CGLIB chủ yếu vì khả năng proxy lớp thường, không phải lý do hiệu năng.
Cải Tiến Singleton Pattern
Trong Spring, scope singleton (mặc định) đảm bảo chỉ tồn tại một instance. Với Java hiện đại, cách triển khai singleton an toàn luồng hiệu quả nhất là sử dụng enum:
public enum ServiceInstance {
INSTANCE;
private final CoreService service;
ServiceInstance() {
service = new CoreService();
}
public CoreService getService() {
return service;
}
}
Phương pháp double-check locking truyền thống:
public class LegacySingleton {
private static volatile LegacySingleton instance;
public static LegacySingleton getInstance() {
if (instance == null) {
synchronized (LegacySingleton.class) {
if (instance == null) {
instance = new LegacySingleton();
}
}
}
return instance;
}
}
Đã trở thành anti-pattern do phức tạp và dễ mắc lỗi. Enum singleton được Effective Java khuyến nghị vì đảm bảo an toàn luồng, đơn giản và hỗ trợ serialization.
Flyweight Pattern trong Tối Ưu Tài Nguyên
Mẫu flyweight tập trung vào chia sẻ đối tượng để giảm tiêu thụ bộ nhớ. Cơ chế hoạt động dựa trên bộ đệm với key nhận dạng:
public class ResourcePool {
private static final Map<String, Resource> cache = new ConcurrentHashMap<>();
public static Resource acquire(String key) {
return cache.computeIfAbsent(key, k -> new HeavyResource(k));
}
}
Đây chính là nền tảng của các object pool trong ứng dụng thực tế. Lưu ý quan trọng: cùng một đoạn code có thể được xem là flyweight (khi nhấn mạnh tái sử dụng) hoặc strategy pattern (khi tập trung vào hành vi).
Prototype Pattern: Thực Tế và Hạn Chế
Mẫu prototype sử dụng phương thức clone() để tạo bản sao đối tượng. Tuy nhiên trong thực tế:
- Shallow copy không đủ cho các đối tượng phức tạp
- Deep copy đòi hỏi triển khai phức tạp trong
clone() - Phương án thay thế hiệu quả hơn: serialization hoặc chuyển đổi JSON
Spring sử dụng cơ chế prototype scope nhưng không dựa trên clone, thay vào đó dùng reflection để khởi tạo instance mới mỗi lần yêu cầu.