DESIGN PATTERN
CÁC CHỦ ĐỀ
BÀI MỚI NHẤT
MỚI CẬP NHẬT

Singleton Pattern trong Java - Cách triển khai và khi nào dùng

Trong bài viết này ta sẽ tìm hiểu Singleton Pattern trong Java. Đây là một khái niệm hoàn toàn mới khi theo học lập trình Java.

test php

banquyen png
Bài viết này được đăng tại freetuts.net, không được copy dưới mọi hình thức.

Qua bài này sẽ giúp các bạn hiểu được các khái niệm liên quan đến Singleton Design và cách hoạt động cũng như cách sử dụng nó như thế nào trong ngôn ngữ lập trình Java.

1. Singleton Pattern là gì?

Singleton Pattern là một trong những loại Design Pattern nằm trong Gangs of Four Design patterns (là một cuốn sách rất nổi tiếng viết về Design Pattern) và nó thuộc danh mục Creational Design Pattern.

Singleton Pattern đảm bảo rằng một lớp chỉ có một instance duy nhất và trong đó cung cấp một cổng giao tiếp chung nhất để truy cập vào lớp đó.

Bài viết này được đăng tại [free tuts .net]

Việc triển khai mẫu Java Singleton luôn là một chủ đề gây tranh cãi giữa các Developers. Ở đây chúng ta sẽ tìm hiểu về các nguyên tắc Singleton Design, các cách khác nhau để thực hiện Singleton Design và một số ví tốt khi sử dụng nó.

Đặc điểm của Singleton Pattern

Dưới đây là một số đặc điểm chung:

  • Giúp hạn chế việc khởi tạo một class và đảm bảo rằng chỉ tồn tại một instance duy nhất trong class.
  • Class Singleton phải cung cấp một điểm truy cập toàn cục để lấy instance của lớp.
  • Được sử dụng để ghi nhật ký, trình điều khiển đối tượng, bộ đệm và nhóm luồng.
  • Được sử dụng trong các pattern khác như: Abstract Factory, Builder, Prototype, Facade etc.
  • Được sử dụng trong các lớp Java như: java.lang.Runtime, java.awt.Desktop.

Triển khai Java Singleton Pattern

Để thực hiện một Singleton Pattern, chúng ta có các cách tiếp cận khác nhau nhưng tất cả chúng đều có các khái niệm chung sau đây:

  • Private Constructor của class để đảm bảo rằng class khác không thể truy cập vào constructor và tạo ra instance mới.
  • Tạo một biến Private static để đảm bảo rằng nó là duy nhất và chỉ được tạo ra trong class đó thôi.
  • Để các class khác có thể truy cập vào instance của class này thì chúng ta cần tạo một Public static method trả về giá trị instance trên.

Trong các phần tiếp theo, chúng ta sẽ tìm hiểu các cách tiếp cận khác nhau về việc triển khai Singleton Pattern.

  • Eager initialization.
  • Static block initialization.
  • Lazy Initialization.
  • Thread Safe Singleton.
  • Bill Pugh Singleton Implementation.
  • Using Reflection to destroy Singleton Pattern.
  • Enum Singleton.
  • Serialization and Singleton.

2. Eager initialization

Trong cách khởi tạo Eager initialization, instance của Singleton class được tạo tại thời điểm loading class (tải lớp). Đây là một phương pháp dể nhất để tạo một class Singleton, nhưng có một nhược điểm là cá thể đó được tạo ra mặc dù người dùng có thể không sử dụng nó.

Dưới đây là việc khởi tạo Initialization Singleton:

public class EagerInitializedSingleton {
    
    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
    
    private EagerInitializedSingleton(){}

    public static EagerInitializedSingleton getInstance(){
        return instance;
    }
}

Nếu lớp Singleton của bạn không sử dụng nhiều tài nguyên, đây là cách tiếp cận để sử dụng. Nhưng trong hầu hết các trường hợp, các lớp Singleton được tạo cho các tài nguyên như: Hệ thống tệp (File System), kết nối cơ sở dữ liệu (Database connections), v.v.

Chúng ta nên tránh việc khởi tạo cho đến khi khách hàng gọi phương thức getInstance. Ngoài ra, phương pháp này không cung cấp bất kỳ tùy chọn nào cho việc xử lý ngoại lệ.

3. Static block initialization

Triển khai khởi tạo Static block initilization tương tự như khởi tạo Eager initilization.

public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;
    
    private StaticBlockSingleton(){}
    
    static{
        try{
            instance = new StaticBlockSingleton();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }
    
    public static StaticBlockSingleton getInstance(){
        return instance;
    }
}

Cả khởi tạo Eager initilization và khởi tạo Static block initilization đều tạo ra cá thể ngay cả trước khi nó được sử dụng và đó không phải là cách thực hành tốt nhất để sử dụng. Vì vậy, trong các phần tiếp theo, chúng ta sẽ tìm hiểu cách tạo một lớp Lazy Initialization.

4. Lazy Initialization

Phương thức khởi tạo Lazy Initialization để triển khai mẫu Singleton tạo ra thể hiện trong phương thức truy cập toàn cầu. Dưới đây là mẫu để tạo lớp Singleton với phương pháp này.

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;
    
    private LazyInitializedSingleton(){}
    
    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

Việc triển khai ở trên hoạt động tốt trong trường hợp môi trường đơn luồng (single-threaded) nhưng khi nói đến các hệ thống đa luồng (multithreaded systems), nó có thể gây ra sự cố nếu nhiều luồng nằm trong điều kiện if cùng một lúc. Nó sẽ phá hủy mẫu singleton và cả hai luồng sẽ nhận được các thể hiện khác nhau của lớp singleton.

5. Thread Safe Singleton

Cách dễ dàng hơn để tạo một lớp Thread Safe Singleton là làm cho phương thức truy cập toàn cục được đồng bộ hóa, để chỉ một luồng có thể thực thi phương thức này tại một thời điểm. Thực hiện chung của phương pháp này giống như lớp dưới đây.

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;
    
    private ThreadSafeSingleton(){}
    
    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }
}

Việc triển khai Thread Safe Singleton hoạt động khá tốt và cung cấp sự an toàn của luồng, nhưng nó chạy rất chậm và tốn hiệu năng, hãy cùng xem đoạn code đã được cải tiến dưới đây:

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

Thay vì chúng ta Thread Safe cả menthod getInstance() thì chúng ta chỉ cần Thread Safe một đoạn code quan trọng thôi.

6. Bill Pugh Singleton Implementation

Các cách tiếp cận ở trên không thể sử dụng trong một số trường hợp nhất định khi có quá nhiều luồng cố gắng lấy instance của lớp Singleton. Vì vậy, Bill Pugh đã đưa ra một cách tiếp cận khác để tạo ra lớp Singleton bằng cách sử dụng một lớp trợ giúp tĩnh bên trong. Bill Pugh Singleton Implementation được khởi tạo như sau:

package com.journaldev.singleton;

public class BillPughSingleton {

    private BillPughSingleton(){}
    
    private static class SingletonHelper{
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
    
    public static BillPughSingleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}

Đây là cách tiếp cận được sử dụng rộng rãi nhất cho lớp Singleton vì nó không yêu cầu đồng bộ hóa.

7. Sử dụng Reflection để hủy Singleton Pattern

Sử dụng Reflection để hủy Singleton Pattern.

import java.lang.reflect.Constructor;

public class ReflectionSingletonTest {

    public static void main(String[] args) {
        EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton instanceTwo = null;
        try {
            Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                constructor.setAccessible(true);
                instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(instanceOne.hashCode());
        System.out.println(instanceTwo.hashCode());
    }
}

Khi bạn chạy thử đoạn code trên, bạn sẽ thấy rằng hashCode của hai trường hợp không giống nhau Reflection được sử dụng rất nhiều trong các Framework như: Spring, Hibernate,..

8. Enum Singleton

Để khắc phục tình trạng này với Reflection, Joshua Bloch đề nghị sử dụng Enum để triển khai mẫu thiết kế Singleton vì Java đảm bảo rằng mọi giá trị Enum chỉ được khởi tạo một lần trong chương trình Java. Vì các giá trị Java Enum có thể truy cập được trên toàn cầu, nên singleton cũng vậy. Hạn chế là loại enum có phần không linh hoạt; ví dụ, nó không cho phép khởi tạo Eager initialization.

public enum EnumSingleton {

    INSTANCE;
    
    public static void doSomething(){
        //do something
    }
}

9. Serialization và Singleton

Đôi khi trong các hệ thống phân tán, chúng ta cần triển khai giao diện Serialization trong lớp Singleton để có thể lưu trữ trạng thái của nó trong hệ thống tệp và truy xuất nó vào một thời điểm sau. Đây là một lớp singleton nhỏ cũng thực hiện giao diện Serialization.

SerializedSingleton.java
import java.io.Serializable;

public class SerializedSingleton implements Serializable{

    private static final long serialVersionUID = -7604766932017737115L;

    private SerializedSingleton(){}
    
    private static class SingletonHelper{
        private static final SerializedSingleton instance = new SerializedSingleton();
    }
    
    public static SerializedSingleton getInstance(){
        return SingletonHelper.instance;
    }
}

Sau đây chúng ta sẽ thực hiện một chương trình đơn giản để kiểm tra:

SingletonSerializedTest.java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class SingletonSerializedTest {
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        SerializedSingleton instanceOne = SerializedSingleton.getInstance();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "filename.ser"));
        SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
        in.close();

        System.out.println("Giá trị hashCode thứ nhất: "+instanceOne.hashCode());
        System.out.println("Giá trị hashCode thứ hai: "+instanceTwo.hashCode());
        System.out.println("-----------------------------");
        System.out.println("Chương trình này được đăng tại Freetuts.net");
    }
}

Kết quả sau khi chạy chương trình:

singleton pattern JPG

Như vậy là chúng ta đã lần lượt tìm hiểu về Singleton Pattern trong mục Design Pattern Java. Mình hy vọng qua bài viết này sẽ giúp các bạn nắm bắt được các kiến thức cơ bản về Singleton Pattern. Bởi vì đây là một trong những kỹ thuật rất quan trọng khi lập trình, nó giúp các bạn tối ưu hóa và kiểm soát code rất tốt.

Cùng chuyên mục:

Khi nào dùng Default Methods trong Java 8

Khi nào dùng Default Methods trong Java 8

Ở 2 bài trước chúng ta đã tìm hiểu 2 tính năng mới của Java…

Cách chuyển chữ hoa thành chữ thường trong Java

Cách chuyển chữ hoa thành chữ thường trong Java

Trong bài viết này chúng ta sẽ tìm hiểu về cách chuyển đổi chữ in…

Bài tập tính tổng các số tự nhiên trong Java

Bài tập tính tổng các số tự nhiên trong Java

Các số dương 1, 2, 3, 4, ... được gọi là các số tự nhiên,…

Cách chuyển chữ thường thành chữ hoa trong Java

Cách chuyển chữ thường thành chữ hoa trong Java

Trong chuỗi có thể vừa có ký tự thường vừa có ký tự hoa, nhưng…

Cách viết hoa ký tự đầu tiên trong Java

Cách viết hoa ký tự đầu tiên trong Java

Để hiểu được bài này, các bạn cần có kiến thức căn bản về Java…

Hướng dẫn chuyển đổi giờ phút giây trong Java

Hướng dẫn chuyển đổi giờ phút giây trong Java

Để hiểu được chương trình, các bạn cần có kiến thức cơ bản về Java.…

Cách lấy thời gian hiện tại trong Java

Cách lấy thời gian hiện tại trong Java

Để hiểu được bài viết này, các bạn cần có kiến thức cơ bản sau…

Cách làm tròn số trong Java

Cách làm tròn số trong Java

Khi thực hiện tính toán, việc kết quả ra một con số thập phân dài…

Cách tìm ma trận chuyển vị trong Java

Cách tìm ma trận chuyển vị trong Java

Quá trình hoán đổi giữa hàng và cột được gọi là chuyển vị của ma…

Cách chuyển ArrayList thành mảng và ngược lại trong Java

Cách chuyển ArrayList thành mảng và ngược lại trong Java

Để hiểu được bài này, các bạn cần có kiến thức cơ bản về mảng…

Cách nối hai mảng trong Java

Cách nối hai mảng trong Java

Mình sẽ thực hiện hai chương trình nối mảng. Chương trình thứ nhất nối hai…

Cách xóa khoảng trắng của chuỗi trong Java

Cách xóa khoảng trắng của chuỗi trong Java

Mình sẽ thực hiện hai chương trình khác nhau để các bạn có thể hiểu…

In ra tam giác bằng ký tự * và số trong Java

In ra tam giác bằng ký tự * và số trong Java

Mình sẽ giới thiệu cách để in ra các tam giác bằng ký tự *…

Tìm số lớn nhất trong mảng Java

Tìm số lớn nhất trong mảng Java

Các bạn cần tìm hiểu về mảng, cách khởi tạo và in mảng trong Java…

Tìm ước của một số nguyên trong Java

Tìm ước của một số nguyên trong Java

Trong bài viết này chúng ta sẽ tìm hiểu cách tìm tất cả các ước…

Cách kiểm tra số hoàn hảo trong Java

Cách kiểm tra số hoàn hảo trong Java

Cách kiểm tra số đối xứng trong Java

Cách kiểm tra số đối xứng trong Java

Trong bài viết này chúng ta sẽ kiểm tra một số có phải là số…

Đảo ngược một số trong Java

Đảo ngược một số trong Java

Mình sẽ giới thiệu các bạn cách đảo ngược một số sử dụng vòng lặp…

Tìm bội chung nhỏ nhất trong Java

Tìm bội chung nhỏ nhất trong Java

Mình sẽ sử dụng hai cách khác nhau để tìm BCNN. Cách thứ nhất mình…

Cách hoán đổi hai số trong Java

Cách hoán đổi hai số trong Java

Trong phần này mình sẽ sử dụng một biến tạm temp() làm biến trung gian…

Top