Tối ưu Hiệu năng bằng Mẫu Thiết kế: Phân tích Thực tế

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.

Thẻ: Spring AOP Arthas JMH Java Concurrency object pooling

Đăng vào ngày 23 tháng 6 lúc 18:20