单例模式

概念

单例模式是一种创建型设计模式,它能够保证一个类只有一个实例,该单例对象必须由单例类自行创建,并提供一个访问该实例的全局节点。该方法可以创建一个新对象,但如果该对象已经被创建,则返回已有的对象

例如,Windows中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

单例模式结构

懒汉式单例

该模式的特点是类加载时没有生成单例,只有当第一次调用getlnstance方法时才去创建这个单例。实现时,仅需隐藏构造函数实现一个静态的构建方法即可。

线程不安全

package singleton;

/**
 * 懒汉式单例(线程不安全)
 * private隐藏构造函数;static实现一个静态的构建方法
 */
public class LazySingletonUnsafe {

    private static LazySingletonUnsafe instance = null;
    public String value;

    private LazySingletonUnsafe(String value) {
        // The following code emulates slow initialization.
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.value = value;
    }

    public static LazySingletonUnsafe getInstance(String value) {
        if (instance == null) {
            instance = new LazySingletonUnsafe(value);
        }
        // 由于instance是static,所以当已存在时,不重复创建
        return instance;
    }
}

单线程测试代码

package singleton;

/**
 * 使用单线程来测试懒汉单例模式
 * @author chenzufeng
 */
public class SingleThread {
    public static void main(String[] args) {
        System.out.println("If you see the same value, then singleton was reused !" + "\n" +
                "If you see different values, then 2 singletons were created !" + "\n" +
                "RESULT :");
        LazySingletonUnsafe lazySingletonUnsafe = LazySingletonUnsafe.getInstance("A");
        LazySingletonUnsafe lazySingletonUnsafe1 = LazySingletonUnsafe.getInstance("B");

        System.out.println(lazySingletonUnsafe.value);
        System.out.println(lazySingletonUnsafe1.value);
    }
}

输出结果:

If you see the same value, then singleton was reused !
If you see different values, then 2 singletons were created !
RESULT :
A
A

多线程测试代码

package singleton;

/**
 * 多线程来测试懒汉单例模式(线程不安全)
 * @author chenzufeng
 * @date 2021-2-8
 */
public class MultiThread {
    public static void main(String[] args) {
        System.out.println("If you see the same value, then singleton was reused !" + "\n" +
                "If you see different values, then 2 singletons were created !" + "\n" +
                "RESULT :");

        Thread threadA = new Thread(new ThreadA());
        Thread threadB = new Thread(new ThreadB());

        threadA.start();
        threadB.start();
    }

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            LazySingletonUnsafe lazySingletonUnsafe = LazySingletonUnsafe.getInstance("A");
            System.out.println(lazySingletonUnsafe.value);
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            LazySingletonUnsafe lazySingletonUnsafe = LazySingletonUnsafe.getInstance("B");
            System.out.println(lazySingletonUnsafe.value);
        }
    }
}

输出结果,有可能没有体现出单例:

If you see the same value, then singleton was reused !
If you see different values, then 2 singletons were created !
RESULT :
A
B

线程安全:双检锁

package singleton;

/**
 * 懒汉式单例(线程安全)
 * private隐藏构造函数;static实现一个静态的构建方法
 * @author chenzufeng
 * @date 2021-2-8
 */
public class LazySingletonSafe {
    private static volatile LazySingletonSafe instance;
    public String value;

    private LazySingletonSafe(String value) {
        this.value = value;
    }

    public static LazySingletonSafe getInstance(String value) {
        /**
         * The approach taken here is called double-checked locking (DCL). 
         * It exists to prevent race condition between multiple threads that may
         * attempt to get singleton instance at the same time, creating separate instances as a result.
         *  
         *  It may seem that having the `result` variable here is completely pointless. 
         *  There is, however, a very important caveat when implementing double-checked locking in Java, 
         *  which is solved by introducing this local variable.
         *         
         */
        LazySingletonSafe result = instance;
        if (result != null) {
            return result;
        }

        synchronized (LazySingletonSafe.class) {
            if (instance == null) {
                instance = new LazySingletonSafe(value);
            }
            return instance;
        }
    }
}

测试代码:

package singleton;

/**
 * 多线程来测试懒汉单例模式(线程安全)
 */
public class TestLazySingletonSafe {
    public static void main(String[] args) {
        System.out.println("If you see the same value, then singleton was reused !" + "\n" +
                "If you see different values, then 2 singletons were created !" + "\n" +
                "RESULT :");

        new Thread(
                () -> {
                    LazySingletonSafe lazySingletonSafe = LazySingletonSafe.getInstance("A");
                    System.out.println(lazySingletonSafe.value);
                }
        ).start();

        new Thread(
                () -> {
                    LazySingletonSafe lazySingletonSafe = LazySingletonSafe.getInstance("B");
                    System.out.println(lazySingletonSafe.value);
                }
        ).start();
    }
}

输出结果:

If you see the same value, then singleton was reused !
If you see different values, then 2 singletons were created !
RESULT :
A
A

采用双锁机制,安全且在多线程情况下能保持高性能。

饿汉式单例

该模式的特点是类一旦加载就创建一个单例,保证在调用getInstance方法之前单例已经存在了:

package singleton;

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

    private HungrySingleton() {}

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

没有加锁,执行效率会提高;但类加载时就初始化,浪费内存。

posted @ 2021-03-11 17:50  chenzufeng  阅读(117)  评论(0)    收藏  举报