JavaRush /Blog Java /Random-VI /Mẫu thiết kế: Singleton

Mẫu thiết kế: Singleton

Xuất bản trong nhóm
Xin chào! Hôm nay chúng ta sẽ xem xét kỹ hơn các mẫu thiết kế khác nhau và chúng ta sẽ bắt đầu với mẫu Singleton, còn được gọi là “singleton”. Mẫu thiết kế: Singleton - 1Hãy nhớ: chúng ta biết gì về các mẫu thiết kế nói chung? Các mẫu thiết kế là những phương pháp hay nhất có thể được áp dụng để giải quyết một số vấn đề đã biết. Các mẫu thiết kế thường không bị ràng buộc với bất kỳ ngôn ngữ lập trình nào. Hãy coi chúng như một tập hợp các đề xuất, theo đó bạn có thể tránh được sai lầm và không phải phát minh lại bánh xe của mình.

Singleton là gì?

Singleton là một trong những mẫu thiết kế đơn giản nhất có thể áp dụng cho một lớp. Đôi khi mọi người nói “lớp này là một singleton”, nghĩa là lớp này triển khai mẫu thiết kế singleton. Đôi khi cần phải viết một lớp mà chỉ có thể tạo một đối tượng. Ví dụ: một lớp chịu trách nhiệm ghi nhật ký hoặc kết nối với cơ sở dữ liệu. Mẫu thiết kế Singleton mô tả cách chúng ta có thể hoàn thành nhiệm vụ đó. Singleton là một mẫu thiết kế thực hiện hai việc:
  1. Cung cấp sự đảm bảo rằng một lớp sẽ chỉ có một thể hiện của lớp đó.

  2. Cung cấp một điểm truy cập toàn cầu cho một thể hiện của lớp này.

Do đó, có hai tính năng đặc trưng của hầu hết mọi lần triển khai mẫu đơn:
  1. Nhà xây dựng tư nhân. Hạn chế khả năng tạo các đối tượng lớp bên ngoài chính lớp đó.

  2. Một phương thức tĩnh công khai trả về một thể hiện của lớp. Phương pháp này được gọi là getInstance. Đây là điểm truy cập toàn cầu vào thể hiện của lớp.

Tùy chọn triển khai

Mẫu thiết kế singleton được sử dụng theo nhiều cách khác nhau. Mỗi lựa chọn đều tốt và xấu theo cách riêng của nó. Ở đây, như mọi khi: không có lý tưởng nào cả, nhưng bạn cần phải phấn đấu vì nó. Nhưng trước hết, hãy xác định điều gì là tốt và điều gì là xấu cũng như số liệu nào ảnh hưởng đến việc đánh giá việc triển khai một mẫu thiết kế. Hãy bắt đầu với sự tích cực. Dưới đây là các tiêu chí mang lại sự thành công và hấp dẫn cho việc thực hiện:
  • Khởi tạo lười biếng: khi một lớp được tải trong khi ứng dụng đang chạy chính xác khi cần thiết.

  • Tính đơn giản và minh bạch của mã: tất nhiên, số liệu này mang tính chủ quan nhưng quan trọng.

  • An toàn luồng: hoạt động chính xác trong môi trường đa luồng.

  • Hiệu suất cao trong môi trường đa luồng: các luồng chặn lẫn nhau ở mức tối thiểu hoặc hoàn toàn không chặn nhau khi chia sẻ tài nguyên.

Bây giờ là nhược điểm. Chúng tôi liệt kê các tiêu chí cho thấy việc triển khai chưa được tốt:
  • Khởi tạo không lười biếng: khi một lớp được tải khi ứng dụng khởi động, bất kể nó có cần thiết hay không (một nghịch lý, trong thế giới CNTT thà lười biếng)

  • Độ phức tạp và khả năng đọc mã kém. Số liệu cũng mang tính chủ quan. Chúng tôi sẽ cho rằng nếu máu chảy ra từ mắt thì việc thực hiện chỉ ở mức bình thường.

  • Thiếu sự an toàn của chủ đề. Nói cách khác, “mối nguy hiểm về sợi chỉ”. Hoạt động không chính xác trong môi trường đa luồng.

  • Hiệu suất kém trong môi trường đa luồng: các luồng luôn chặn lẫn nhau hoặc thường xuyên khi chia sẻ tài nguyên.

Mã số

Bây giờ chúng tôi đã sẵn sàng xem xét các tùy chọn triển khai khác nhau, liệt kê những ưu và nhược điểm:

Giải pháp đơn giản

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
Việc thực hiện đơn giản nhất. Ưu điểm:
  • Tính đơn giản và minh bạch của mã

  • An toàn chủ đề

  • Hiệu suất cao trong môi trường đa luồng

Nhược điểm:
  • Không lười khởi tạo.
Trong nỗ lực sửa lỗi cuối cùng, chúng tôi nhận được cách triển khai thứ hai:

Khởi tạo lười biếng

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Ưu điểm:
  • Khởi tạo lười biếng.

Nhược điểm:
  • Không an toàn cho chủ đề

Việc thực hiện là thú vị. Chúng ta có thể khởi tạo một cách lười biếng, nhưng chúng ta đã mất đi sự an toàn của luồng. Không có vấn đề gì: trong quá trình triển khai số ba, chúng tôi đồng bộ hóa mọi thứ.

Trình truy cập được đồng bộ hóa

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Ưu điểm:
  • Khởi tạo lười biếng.

  • An toàn chủ đề

Nhược điểm:
  • Hiệu suất kém trong môi trường đa luồng

Tuyệt vời! Trong lần triển khai thứ ba, chúng tôi đã mang lại sự an toàn cho luồng! Đúng, nó chậm... Bây giờ phương thức getInstanceđã được đồng bộ hóa và bạn chỉ có thể nhập từng phương thức một. Trên thực tế, chúng ta không cần phải đồng bộ hóa toàn bộ phương thức mà chỉ cần đồng bộ hóa phần đó trong đó chúng ta khởi tạo một đối tượng lớp mới. Nhưng chúng ta không thể đơn giản gói synchronizedphần chịu trách nhiệm tạo đối tượng mới vào một khối: điều này sẽ không mang lại sự an toàn cho luồng. Nó phức tạp hơn một chút. Phương pháp đồng bộ hóa chính xác được đưa ra dưới đây:

Khóa kiểm tra kép

public class Singleton {
    private static Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
Ưu điểm:
  • Khởi tạo lười biếng.

  • An toàn chủ đề

  • Hiệu suất cao trong môi trường đa luồng

Nhược điểm:
  • Không được hỗ trợ trên các phiên bản Java thấp hơn 1.5 (từ khóa dễ bay hơi đã được sửa trong phiên bản 1.5)

Tôi lưu ý rằng để tùy chọn triển khai này hoạt động chính xác, cần phải có một trong hai điều kiện. Biến INSTANCEphải là final, hoặc volatile. Việc triển khai cuối cùng mà chúng ta sẽ thảo luận hôm nay là Class Holder Singleton.

Người giữ lớp Singleton

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
Ưu điểm:
  • Khởi tạo lười biếng.

  • An toàn chủ đề.

  • Hiệu suất cao trong môi trường đa luồng.

Nhược điểm:
  • Để hoạt động chính xác, cần đảm bảo rằng đối tượng lớp Singletonđược khởi tạo không có lỗi. Nếu không, lệnh gọi phương thức đầu tiên getInstancesẽ kết thúc bằng lỗi ExceptionInInitializerErrorvà tất cả các lệnh gọi tiếp theo sẽ thất bại NoClassDefFoundError.

Việc thực hiện gần như hoàn hảo. Và lười biếng, an toàn theo luồng và nhanh chóng. Nhưng có một sắc thái được mô tả trong phần trừ. Bảng so sánh các cách triển khai khác nhau của mẫu Singleton:
Thực hiện Khởi tạo lười biếng An toàn chủ đề Tốc độ đa luồng Khi nào nên sử dụng?
Giải pháp đơn giản - + Nhanh Không bao giờ. Hoặc khi việc khởi tạo lười biếng không quan trọng. Nhưng không bao giờ tốt hơn.
Khởi tạo lười biếng + - Không áp dụng Luôn luôn khi không cần đa luồng
Trình truy cập được đồng bộ hóa + + Chậm Không bao giờ. Hoặc khi tốc độ làm việc với đa luồng không thành vấn đề. Nhưng không bao giờ tốt hơn
Khóa kiểm tra kép + + Nhanh Trong những trường hợp hiếm hoi khi bạn cần xử lý các trường hợp ngoại lệ khi tạo một singleton. (khi Class Holder Singleton không được áp dụng)
Người giữ lớp Singleton + + Nhanh Luôn luôn khi cần đa luồng và có sự đảm bảo rằng một đối tượng lớp đơn sẽ được tạo mà không gặp vấn đề gì.

Ưu và nhược điểm của mẫu Singleton

Nói chung, singleton thực hiện chính xác những gì được mong đợi ở nó:
  1. Cung cấp sự đảm bảo rằng một lớp sẽ chỉ có một thể hiện của lớp đó.

  2. Cung cấp một điểm truy cập toàn cầu cho một thể hiện của lớp này.

Tuy nhiên, mô hình này có nhược điểm:
  1. Singleton vi phạm SRP (Nguyên tắc trách nhiệm duy nhất) - lớp Singleton ngoài trách nhiệm trước mắt còn kiểm soát số lượng bản sao của nó.

  2. Sự phụ thuộc của một lớp hoặc phương thức thông thường vào một singleton không được hiển thị trong hợp đồng công khai của lớp.

  3. Biến toàn cầu là xấu. Singleton cuối cùng biến thành một biến toàn cục khổng lồ.

  4. Sự hiện diện của một singleton làm giảm khả năng kiểm thử của ứng dụng nói chung và các lớp sử dụng singleton nói riêng.

Được rồi, mọi chuyện đã kết thúc rồi. Chúng tôi đã xem xét mẫu thiết kế singleton. Giờ đây, trong cuộc trò chuyện suốt đời với những người bạn lập trình viên của mình, bạn sẽ có thể nói không chỉ những điều tốt về nó mà còn một vài từ về những điều xấu về nó. Chúc may mắn trong việc nắm vững kiến ​​thức mới.

Bài đọc bổ sung:

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION