Giới thiệu

Bài viết này giới thiệu về Lazy Initialization — một kỹ thuật phổ biến trong Java để trì hoãn việc tạo đối tượng cho đến khi nó thực sự cần sử dụng.

Vấn đề ban đầu

Trong ví dụ tiêu chuẩn sau:
class Demo {
    private Collaborator collaborator = new Collaborator();

    public Collaborator getCollaborator() {
        return collaborator;
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        Collaborator collaborator = demo.getCollaborator();
    }
}
Trong đoạn này, mỗi khi Demo được tạo, đối tượng Collaborator cũng được tạo ngay lập tức — dù có lúc bạn chưa cần dùng đến.

Ý tưởng của Lazy Initialization

Để tiết kiệm tài nguyên và tránh tạo đối tượng không cần thiết, ta chỉ nên khởi tạo Collaborator khi thực sự được yêu cầu — tức là khi getCollaborator() được gọi lần đầu.
Cách làm như sau:
class Demo {
    private Collaborator collaborator;

    public Collaborator getCollaborator() {
        if (collaborator == null) {
            collaborator = new Collaborator();
        }
        return collaborator;
    }
}
👉 Đoạn này có nghĩa là:
Collaborator chưa được tạo khi Demo khởi tạo.
Chỉ khi getCollaborator() gọi, ta mới kiểm tra null và tạo mới nếu cần.
➡️ Đây chính là Lazy Initialization.

Nhược điểm trong môi trường đa luồng

Trong môi trường chạy đa luồng (multi-thread), đoạn code trên có thể gây lỗi như:
Hai luồng cùng gọi getCollaborator() đồng thời khi collaborator == null,
Khi đó cả hai luồng có thể tạo hai đối tượng khác nhau, phá vỡ mong muốn chỉ có một.

Giải pháp với synchronized

Để đảm bảo an toàn trong đa luồng, ta có thể sử dụng synchronized:
public synchronized Collaborator getCollaborator() {
    if (collaborator == null) {
        collaborator = new Collaborator();
    }
    return collaborator;
}
➡️ Điều này đảm bảo rằng chỉ một luồng tại một thời điểm được phép tạo Collaborator.

Tối ưu với Double-Check Locking

Đoạn code synchronized trên có thể làm giảm hiệu năng vì mỗi lần gọi đều lock. Ta có thể tối ưu bằng cách dùng Double-Check Locking:
public Collaborator getCollaborator() {
    if (collaborator == null) {
        synchronized(this) {
            if (collaborator == null) {
                collaborator = new Collaborator();
            }
        }
    }
    return collaborator;
}
Ngoại lệ: để an toàn hơn trong Java, trường collaborator cần được khai báo volatile.
➡️ Đây là cách phổ biến để đảm bảo chỉ tạo một đối tượng duy nhất ngay cả trong nhiều luồng cùng lúc.

Tóm tắt ý chính

  • Lazy Initialization là kỹ thuật trì hoãn tạo đối tượng cho đến khi cần dùng.
  • Giúp tiết kiệm tài nguyên, bộ nhớ và chi phí khởi tạo nếu đối tượng nặng.
✅ Cần chú ý đồng bộ hoá trong môi trường đa luồng để tránh tạo nhiều đối tượng không mong muốn.
Nguồn bài viết dịch từ ryukato.github.io