设计模式之单例模式(singleton)

单例模式

特点

  1. 构造器需要私有化
  2. 提供访问点

优点
节省内存,严格控制访问

缺点
难以扩展,严格意义上讲不符合开闭原则

饿汉模式

public class HungrySingleton {

    private static final HungrySingleton instance = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return instance;
    }

    public static void main(String[] args) {

        HungrySingleton instance = HungrySingleton.getInstance();
        System.out.println(instance);
    }
}

缺点 : 可以通过序列化破坏单例

解决 : 可以增加readResoResolve()覆盖流处理,来保证单例

懒汉模式

线程不安全

public class UnSafeSingleton {

    private  static UnSafeSingleton unSafeSingleton;

    private UnSafeSingleton(){}

    public static UnSafeSingleton getInstance(){
        if(null == unSafeSingleton){
            unSafeSingleton = new UnSafeSingleton();
        }
        return unSafeSingleton;
    }

    public static void main(String[] args) {
        System.out.println(UnSafeSingleton.getInstance());
    }
}

不可以在多线程环境下使用,会破坏单例

线程安全

synchronized

public class SafeSingleton {

    private  static SafeSingleton safeSingleton;

    private SafeSingleton(){}

    public static synchronized SafeSingleton getInstance(){
        if(null == safeSingleton){
            safeSingleton = new SafeSingleton();
        }
        return safeSingleton;
    }

    public static void main(String[] args) {
        System.out.println(SafeSingleton.getInstance());
    }
}

static+synchronized会锁住整个class对象,性能低下

double-check

public class DoubleCheckSingleton {

    private  static DoubleCheckSingleton doubleCheckSingleton;

    private DoubleCheckSingleton(){}

    public static DoubleCheckSingleton getInstance(){
        if(null == doubleCheckSingleton){
           synchronized (doubleCheckSingleton){
               if(null == doubleCheckSingleton){
                   doubleCheckSingleton = new DoubleCheckSingleton();
               }
           }
        }
        return doubleCheckSingleton;
    }

    public static void main(String[] args) {
        System.out.println(DoubleCheckSingleton.getInstance());
    }
}

性能稍微提升,但是还是不高

内部类

public class InnerClassSingleton {

    private InnerClassSingleton(){}

    private static class Holder{
        static InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
    }

    private static InnerClassSingleton getInstance(){
        return Holder.innerClassSingleton;
    }

    public static void main(String[] args) {
        System.out.println(InnerClassSingleton.getInstance());
    }
}

缺点 : 可以通过反射破坏单例

枚举式单例

public class EnumSingleton {

    private EnumSingleton(){}
    
    private enum Holder{
        INSTANCE;

        private EnumSingleton enumSingleton;

        Holder(){
            this.enumSingleton = new EnumSingleton();
        }

        public EnumSingleton getInstance(){
            return this.enumSingleton;
        }
    }

    public static void main(String[] args) {
        System.out.println(Holder.INSTANCE.getInstance());
    }
}
  1. 这种方式推荐,首先他性能比较好,在序列化时期,jdk本身保证枚举是单例的.
  2. 这种基于饿汉+懒汉的方式实现

容器单例

线程不安全

public class ContainerSingleton {

    private ContainerSingleton(){}

    static Map<String,Object> container = new ConcurrentHashMap<>();

    public static Object getInstance(String className){
        if(!container.containsKey(className)){
            try {
                container.put(className,Class.forName(className).newInstance());
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return container.get(className);
    }

    public static void main(String[] args) {
        Object instance = ContainerSingleton.getInstance(
                "com.blaaair.singleton.ContainerSingleton");
        Object instance2 = ContainerSingleton.getInstance(
                "com.blaaair.singleton.ContainerSingleton");
        System.out.println(instance);
        System.out.println(instance2);
    }
}

线程安全

public class ContainerSingleton {

    private ContainerSingleton(){}

    static Map<String,Object> container = new ConcurrentHashMap<>();

    public static Object getInstance(String className){
        synchronized (container){
            if(!container.containsKey(className)){
                try {
                    container.put(className,Class.forName(className).newInstance());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return container.get(className);
    }

    public static void main(String[] args) {
        Object instance = ContainerSingleton.getInstance(
                "com.blaaair.singleton.ContainerSingleton");
        Object instance2 = ContainerSingleton.getInstance(
                "com.blaaair.singleton.ContainerSingleton");
        System.out.println(instance);
        System.out.println(instance2);
    }
}

在方法里增加synchronized(){}静态块 ,这里也是spring内部的方式.
ConcurrentHashMap其实也解决不了此处的线程安全问题,因为它只能保证ConcurrentHashMap内部的方法线程安全,但是对于getInstance方法来说还是不安全的

线程间单例(基于ThreadLocal)

不能保证程序全局唯一,但能保证线程唯一

public class ThreadLocalSingleton {

    private ThreadLocalSingleton() { }

    private static ThreadLocal<ThreadLocalSingleton> singletonThreadLocal
            = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };

    public static ThreadLocalSingleton getInstance(){
        return singletonThreadLocal.get();
    }

    public static void main(String[] args) {
        System.out.printf("ThreadName - %s : "+ThreadLocalSingleton.getInstance()+"\n",Thread.currentThread().getName());

        new Thread(new MyRunner()).start();
        new Thread(new MyRunner()).start();
    }
}
class MyRunner implements Runnable{

    @Override
    public void run() {
        System.out.printf("ThreadName - %s : "+ThreadLocalSingleton.getInstance()+"\n",Thread.currentThread().getName());
        System.out.printf("ThreadName - %s : "+ThreadLocalSingleton.getInstance()+"\n",Thread.currentThread().getName());
        System.out.printf("ThreadName - %s : "+ThreadLocalSingleton.getInstance()+"\n",Thread.currentThread().getName());
    }
}

输出

ThreadName - main : com.blaaair.singleton.ThreadLocalSingleton@1b6d3586
ThreadName - Thread-0 : com.blaaair.singleton.ThreadLocalSingleton@43f47252
ThreadName - Thread-1 : com.blaaair.singleton.ThreadLocalSingleton@730b7ffd
ThreadName - Thread-0 : com.blaaair.singleton.ThreadLocalSingleton@43f47252
ThreadName - Thread-1 : com.blaaair.singleton.ThreadLocalSingleton@730b7ffd
ThreadName - Thread-0 : com.blaaair.singleton.ThreadLocalSingleton@43f47252
ThreadName - Thread-1 : com.blaaair.singleton.ThreadLocalSingleton@730b7ffd

每个线程有一个单例对象,所以跨线程间是线程不安全的

原理

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

在ThreadLocal源码中,维护一个ThreadLocalMap,当setInitialValue()或者set()时,map.set(this, value);会把当前线程设置为key,值设置成value
所以此处也证明基于ThreadLocal的单例也是容器式单例
应用场景 : DynamicDataSource数据源


设计模式之单例模式(singleton)
https://www.blaaair.com/archives/she-ji-mo-shi-zhi-dan-li-mo-shi-singleton
作者
Glo6f
发布于
2023年06月16日
许可协议