8种单例模式

单例模式

八种设计模式说明

饿汉式(线程安全)

代码

public class Hungry {
    private static Hungry hungry = new Hungry();

    public Hungry(){}

    public static Hungry getInstance(){
        return hungry;
    }
}

测试代码(以下类似)

public class HungryTest {
    public static void main(String[] args) {
        int count = 1000;
        CountDownLatch latch = new CountDownLatch(count);
        long start = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            new Thread(()->{
                try {
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Hungry.getInstance());
            }).start();
            latch.countDown();
        }
        System.out.println("cost:" + (System.currentTimeMillis() - start) + "ms");
    }
}

设计思想

在类加载的时候就把对象创建并放到内存中

问题

饿汉式单例模式是线程安全的,但是该实例在类装载的时候就加入到内存中,可能会造成资源浪费.测试结果

测试结果(创建1000个)

com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
com.yczuoxin.pattern.singleton.hungry.Hungry@46bf5757
...
cost:139ms

懒汉式(线程不安全)

代码

public class ThreadUnSafeLazy {
    private static ThreadUnSafeLazy lazy;

    private ThreadUnSafeLazy(){}

    public static ThreadUnSafeLazy getInstance(){
        if(null == lazy){
            lazy = new ThreadUnSafeLazy();
        }
        return lazy;
    }
}

设计思想

在获取对象时先去判断是否实例化过,如果没有实例化就实例化一个对象

问题

在高并发环境下,如果一个线程访问时该对象还在实例化过程中,那么就会重新再实例化一个对象,导致线程不安全问题.

测试结果(创建1000个)

com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@7fc8692f
com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@304a7580
com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@304a7580
com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@304a7580
com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@304a7580
com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@304a7580
com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@304a7580
com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@304a7580
com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@304a7580
com.yczuoxin.pattern.singleton.lazy.ThreadUnSafeLazy@304a7580
...
cost:98ms

懒汉式(线程安全)

public class ThreadSafeLazy {
    private static ThreadSafeLazy lazy;

    private ThreadSafeLazy(){}

    public static synchronized ThreadSafeLazy getInstance(){
        if(null == lazy){
            lazy = new ThreadSafeLazy();
        }
        return lazy;
    }
}

设计思想

由线程不安全的懒汉式可以得出该问题出现在同时两个线程调用了getInstance()方法导致,所以给与该方法加上synchronized加锁,使得该方法只有一个线程访问,保证了线程安全.

问题

synchronized锁会降低性能.增加获取实例的时间.

测试结果(创建1000个)

com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
com.yczuoxin.pattern.singleton.lazy.ThreadSafeLazy@72f7f615
...
cost:129ms

枚举类(无需测试)

代码

public enum ColorEnum {
    RED,
    YELLOW,
    BLACK,
    BLUE
}

说明

枚举类可以作为单例模式是由于其特殊的性质,他在反编译的时候已经变成了final类,并且字段都被static final修饰.所以是枚举类初始化的时候,就已经初识化值了,所以也满足了单例模式的条件.

静态内部类

代码

public class StaticInnerClass {

    private StaticInnerClass(){}

    static class StaticInnerClassHolder{
        private static final StaticInnerClass statics = new StaticInnerClass();
    }

    public static StaticInnerClass getInstance(){
        return StaticInnerClassHolder.statics;
    }
}

设计思想

只有在使用静态内部类的时候静态内部类才会实例化,只有使用到了静态内部类才会实例化该对象.不会造成对资源的浪费.

问题

创建对象所用时间较长.

测试结果(创建1000个)

com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
com.yczuoxin.pattern.singleton.statics.StaticInnerClass@2d5a99cd
...
cost:146ms

注册登记式

代码

public class Register {

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

    private Register() {
    }

    static {
        Register register = new Register();
        registerMap.put(Register.class.getName(), register);
    }

    public static Object getInstance(String className) {
        if (null == className){
            className = "com.yczuoxin.pattern.singleton.register.Register";
        }
        if (!registerMap.containsKey(className)){
            try {
                registerMap.put(className,Class.forName(className).newInstance());
            } catch (Exception e) {
                System.out.println("请填写正确的类的全路径");
                e.printStackTrace();
            }
        }
        return registerMap.get(className);
    }
}

设计思想

用一个容器去装载所有的对象,并在容器中用其类的限定名登记所有的对象,如果实例对象在不存在,我们注册到单例注册表中,第二次取的时候根据类的限定名去取出对应的对象.不需要重新去初始化.

问题

暂无

扩展

Spring就是利用这种方式存放各种Bean.

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String)

测试结果(创建1000个)

com.yczuoxin.pattern.singleton.register.Register@4cc45e92
com.yczuoxin.pattern.singleton.register.Register@4cc45e92
com.yczuoxin.pattern.singleton.register.Register@4cc45e92
com.yczuoxin.pattern.singleton.register.Register@4cc45e92
com.yczuoxin.pattern.singleton.register.Register@4cc45e92
com.yczuoxin.pattern.singleton.register.Register@4cc45e92
com.yczuoxin.pattern.singleton.register.Register@4cc45e92
com.yczuoxin.pattern.singleton.register.Register@4cc45e92
com.yczuoxin.pattern.singleton.register.Register@4cc45e92
com.yczuoxin.pattern.singleton.register.Register@4cc45e92
...
cost:108ms

双重校验

代码

public class DoubleCheck {
    private static volatile DoubleCheck doubleCheck;

    private DoubleCheck(){}

    public static DoubleCheck getInstance(){
        if (null == doubleCheck){
            synchronized (DoubleCheck.class){
                if(null == doubleCheck){
                    doubleCheck = new DoubleCheck();
                }
            }
        }
        return doubleCheck;
    }
}

技术思路

利用volite可见性和synchronized锁保证单例的创建是线程安全的.

问题

volite会使缓存失效,消耗性能,synchronized锁也导致性能的消耗,所以总的说来很耗性能.

测试结果(创建1000个)

com.yczuoxin.pattern.singleton.doublecheck.DoubleCheck@7097d410
com.yczuoxin.pattern.singleton.doublecheck.DoubleCheck@7097d410
com.yczuoxin.pattern.singleton.doublecheck.DoubleCheck@7097d410
com.yczuoxin.pattern.singleton.doublecheck.DoubleCheck@7097d410
com.yczuoxin.pattern.singleton.doublecheck.DoubleCheck@7097d410
com.yczuoxin.pattern.singleton.doublecheck.DoubleCheck@7097d410
com.yczuoxin.pattern.singleton.doublecheck.DoubleCheck@7097d410
com.yczuoxin.pattern.singleton.doublecheck.DoubleCheck@7097d410
...
cost:124ms

序列化

代码

public class Serialize implements Serializable {
    private static Serialize serialize = new Serialize();

    private Serialize(){}

    public static Serialize getInstance(){
        return serialize;
    }

    protected Object readResolve(){
        return  serialize;
    }
}

测试代码

public class SerializableTest {
    public static void main(String[] args) {
        Serialize serialize = Serialize.getInstance();
        System.out.println(serialize);
        writeFile("D://serialize.txt", serialize);
        Serialize serialized = (Serialize)readFile("D://serialize.txt");
        System.out.println(serialized);
    }

    private static void writeFile(String path, Object object) {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try{
            fos = new FileOutputStream(new File(path));
            oos = new ObjectOutputStream(fos);
            oos.writeObject(object);
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static Object readFile(String path) {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        Object object = null;
        try{
            fis = new FileInputStream(new File(path));
            ois = new ObjectInputStream(fis);
            object = ois.readObject();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (null != ois){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != fis){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return object;
    }
}

设计思想

利用序列化和反序列来创建对象,为了使创建的对象是单例,必须实现Serializable接口及重写readResolve(),当实现了readResolve方法后,jvm就会有readResolve返回指定对象,也就保证了单例性.

protected Object readResolve(){
    return  serialize;
}

缺点

使用起来比较复杂,还要使用到IO读写

测试结果

com.yczuoxin.pattern.singleton.serialize.Serialize@4554617c
com.yczuoxin.pattern.singleton.serialize.Serialize@4554617c

优点

节省资源空间,减少了new对象所消耗的性能,并且可以在任何地方拿到同样的东西.

缺点

当获取对象时不能用new关键字来创建,而要知道其API,增加了开发人员的使用难度.

使用场景

  1. 工具类
  2. 日志等文件系统
  3. 各种资源池(连接池,线程池等)
  4. 可以循环使用的对象

猜你喜欢

转载自blog.csdn.net/ycxzuoxin/article/details/81413519