Tối ưu hiệu suất bằng mô hình thiết kế: Phân tích trường hợp

Mô hình thiết kế là cách tổng hợp các kỹ thuật lập trình phổ biến, giúp các lập trình viên dễ dàng trao đổi về vấn đề kỹ thuật. Trong bài viết trước, chúng ta đã đề cập đến việc sử dụng mô hình Decorator trong module I/O, điều này giúp dễ hình dung cấu trúc mã nguồn.

Hầu hết các mô hình thiết kế không trực tiếp cải thiện hiệu suất chương trình mà chỉ là cách tổ chức mã. Trong bài này, chúng ta sẽ phân tích một số mô hình liên quan đến hiệu suất như Proxy, Singleton, Flyweight và Prototype.

Tìm nguyên nhân chậm khi sử dụng Proxy động

Spring sử dụng rộng rãi mô hình Proxy thông qua CGLIB để tăng cường byte code. Trong các dự án phức tạp, có rất nhiều đoạn mã AOP như kiểm tra quyền, ghi log. Dưới đây là ví dụ sử dụng Arthas để xác định điểm nghẽn hiệu suất trong Proxy động.

@Component 
public class SampleComponent { 
    public void execute() { 
        System.out.println("*******************"); 
    } 
}
@Aspect 
@Component 
public class LoggingAspect { 
    @Pointcut("execution(* com.example.demo.SampleComponent.*(..))") 
    public void logPointcut() { 
    }  
    @Before("logPointcut()") 
    public void beforeExecution() { 
        System.out.println("before"); 
        try { 
            Thread.sleep(TimeUnit.SECONDS.toMillis(1)); 
        } catch (InterruptedException e) { 
            throw new IllegalStateException(); 
        } 
    } 
}
@Controller 
public class PerformanceTestController { 
    @Autowired 
    private SampleComponent component;  
    @ResponseBody 
    @GetMapping("/test") 
    public String test() { 
        long start = System.currentTimeMillis(); 
        component.execute(); 
        long duration = System.currentTimeMillis() - start; 
        String className = component.getClass().toString(); 
        return className + " | " + duration; 
    } 
}

Kết quả chạy chương trình cho thấy lớp được tạo ra là EnhancerBySpringCGLIB với thời gian thực thi khoảng 1023ms.

Mô hình Proxy

Mô hình Proxy cho phép kiểm soát truy cập đối tượng thông qua lớp đại diện. Java hỗ trợ hai cách tạo Proxy động:

  • JDK Proxy: yêu cầu interface, sử dụng InvocationHandlerProxy
  • CGLIB: có thể proxy lớp cụ thể, sử dụng MethodInterceptorEnhancer

Dưới đây là kết quả benchmark so sánh hiệu suất giữa hai cách:

Benchmark              Mode  Cnt      Score      Error   Units 
ProxyBenchmark.cglib  thrpt   10  78499.580 ± 1771.148  ops/ms 
ProxyBenchmark.jdk    thrpt   10  88948.858 ±  814.360  ops/ms 

Kết quả cho thấy JDK Proxy có hiệu suất tốt hơn CGLIB trong phiên bản Java 1.8.

Mô hình Singleton

Spring sử dụng thuộc tính scope để xác định phạm vi của bean. Khi sử dụng singleton (mặc định), chỉ có một phiên bản duy nhất được tạo trong container.

Cách tạo singleton an toàn cho luồng là sử dụng double-check với từ khóa volatile:

public class Singleton { 
    private static volatile Singleton instance;  

    public static Singleton getInstance() { 
        if (instance == null) { 
            synchronized (Singleton.class) { 
                if (instance == null) { 
                    instance = new Singleton(); 
                } 
            } 
        } 
        return instance; 
    } 
}

Tuy nhiên, cách này hiện đã không còn được khuyến khích. Thay vào đó, nên sử dụng enum để đảm bảo tính an toàn và hiệu quả:

public class EnumSingleton { 
    private EnumSingleton() { 
    } 
    public static EnumSingleton getInstance() { 
        return Holder.INSTANCE; 
    } 
    private enum Holder { 
        INSTANCE; 
        private final EnumSingleton singleton; 
        Holder() { 
            singleton = new EnumSingleton(); 
        } 
    } 
}

Mô hình Flyweight

Mô hình này tối ưu hiệu suất bằng cách chia sẻ các đối tượng giống nhau. Ví dụ điển hình là quản lý các đối tượng Strategy:

Map<String, Strategy> strategies = new HashMap<>(); 
strategys.put("a", new StrategyA()); 
strategys.put("b", new StrategyB()); 

Đoạn mã trên có thể được coi là cả mô hình Flyweight (tái sử dụng đối tượng) và Strategy (tách biệt logic).

Mô hình Prototype

Mô hình này tạo đối tượng mới từ mẫu đã có. Trong Java, phương thức clone() của lớp Object là ví dụ tiêu biểu. Tuy nhiên, việc sử dụng clone() thường gặp khó khăn với đối tượng phức tạp:

public class Prototype implements Cloneable {
    private String data;
    
    public Prototype(String data) {
        this.data = data;
    }
    
    @Override
    public Prototype clone() {
        return new Prototype(this.data);
    }
}

Hiện nay, mô hình Prototype thường được sử dụng như một tư tưởng thiết kế hơn là công cụ tối ưu hiệu suất.

Thẻ: Spring cglib jdk Mô hình Thiết kế tối ưu hiệu suất

Đăng vào ngày 28 tháng 6 lúc 03:11