创建型设计模式之单例模式

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 创建型设计模式之单例模式,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

单例模式

概述

单例模式保证一个对象在JVM中只能有一个实例,减少内存开销,避免对资源的多重占用。单例模式属于创建型模式。

在单例模式中,单例类只能有一个实例,必须由自己创建自己的唯一实例,给所有其他对象提供这一实例。

在这里插入图片描述

常见单例:

懒汉式:就是需要的才会去实例化,线程不安全。

饿汉式:就是当class文件被加载的时候,初始化,天生线程安全。

比如:ServletContext、ServletContextConfig、ApplicationContext等都是单例形式

优缺点

优点:

1.在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。

2.避免对资源的多重占用。

缺点:

1.没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

懒汉式

懒汉式单例模式是对象要在被使用时才会初始化,能解决饿汉式单例可能带来的内存浪费问题

注意:该方式在多线程中使用存在线程安全问题。

public class Singleton {
    //需要时才会被实例化
    private static Singleton singleton;

    private Singleton() {}
    
	public static Singleton getSingleton() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

使用synchronized关键字使方法变成线程同步方法。

这种方法在线程数量较多情况下,如果CPU压力过大,则会导致大批线程阻塞,从而导致程序性能大幅下降。

public class Singleton {
    //需要时才会被实例化
    private static Singleton singleton;

    private Singleton() {

    }
    synchronized public static Singleton getSingleton() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

    public static void main(String[] args) {
        Singleton sl1 = Singleton.getSingleton();
        Singleton sl2 = Singleton.getSingleton();
        System.out.println(sl1 == sl2);
    }

使用双重检查锁的的单例模式可进一步提升性能。

public class Singleton {
    //需要时才会被实例化
    private static Singleton singleton;
    private Singleton() {}
    
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                //检查是否要重新创建实例
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

饿汉式

饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象。它绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题。

适用于单例对象较少的情况。如果系统中有大批量的单例对象存在,那系统初始化是就会导致大量的内存浪费。

优点:能保证绝对线程安全、执行效率比较高

缺点:所有对象类加载的时候就实例化

public class Singleton{
    //当class文件被加载初始化
    private static Singleton singleton = new Singleton();

    private Singleton() {}

    public static Singleton getSingleton() {
        return singleton;
    }
}

  public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();
        System.out.println(singleton1 == singleton2);
    }

上述是标准写法,还有另外一种写法,利用静态代码块的机制

public class Singleton{
    //当class文件被加载初始化
    private static Singleton singleton;

	static{
	 singleton = new Singleton()
	}

    private Singleton() {}

    public static Singleton getSingleton() {
        return singleton;
    }
}

其他实现方式

静态内部类实现单例

由于饿汉式单例模式存在内存浪费问题,懒汉式单例模式有synchronized性能问题。

实际上可以从类的初始化角度,采用静态内部类的方式,利用内部类一定是要在方法调用之前初始化的特点,巧妙避免线程安全问题

public class Singleton {

    private Singleton() {
        if (LazySingleton.INSTANCE != null) {
            throw new RuntimeException("非法访问");
        }
    }

    /**
     * 在返回结果前先加载内部类
     */
    private static Singleton getInstance() {
        return LazySingleton.INSTANCE;
    }

    /**
     * 默认不加载
     */
    private static class LazySingleton {
        private static final Singleton INSTANCE = new Singleton();
    }
}

ThreadLocal实现线程单例

ThreadLocal不能保证其创建的对象是全局唯一的,但是能保证在单个线程中是唯一的,天生是线程安全的

单例模式为了线程安全会给方法加锁,以时间换空间。ThreadLocal将所有的对象全部放在ThreadLocalMap中,为每个线程都提供一个对象,实际上是以空间换时间实现线程隔离。

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
            new ThreadLocal<ThreadLocalSingleton>() {
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton() {
    }

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

    public static void main(String[] args) {
        ThreadLocalSingleton threadLocalSingleton1 = ThreadLocalSingleton.getInstance();
        ThreadLocalSingleton threadLocalSingleton2 = ThreadLocalSingleton.getInstance();
        System.out.println(threadLocalSingleton1 == threadLocalSingleton2);
    }
}

注册式单例模式

注册式单例模式又称为登记式单例模式,将每一个实例都登记到某一个地方,使用唯一的标识获取实例。

注册式单例模式有两种:枚举式单例模式容器式单例模式

枚举式单例模式

枚举式单例模式是比较推荐的一种单例模式实现,写法优雅

注意:与饿汉式类似,在类加载时就将所有对象初始化,不适合大量创建单例对象的场景

public enum EnumSingleton {
    INSTANCE;
    
    public static EnumSingleton getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        EnumSingleton instance1 = EnumSingleton.getInstance();
        EnumSingleton instance2 = EnumSingleton.getInstance();
        System.out.println(instance1 == instance2);
    }
}

容器式单例模式

容器式单例模式适用于需要大量创建单例对象的场景,便于管理。注意:它是非线程安全的。

public class ContainerSingleton {

    private ContainerSingleton() {
    }

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

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

    public static void main(String[] args) {
        Object instance1 = ContainerSingleton.getInstance("cn.ybzy.demo.ContainerSingleton");
        Object instance2 = ContainerSingleton.getInstance("cn.ybzy.demo.ContainerSingleton");
        System.out.println(instance1 == instance2);
    }
}

单例模式的破坏

反射破坏

如果一个单例模式的私有构造不做任何处理,就可以通过反射破坏单例模式

private Singleton() {}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<Singleton> singletonClass = Singleton.class;
        // 反射强制获取私有构造函数
        Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor(null);
        // 强制访问私有构造函数
        declaredConstructor.setAccessible(true);
        // 暴力调用私有构造函数进行初始化
        Singleton singleton1 = declaredConstructor.newInstance();
        Singleton singleton2 = declaredConstructor.newInstance();

        System.out.println(singleton1 == singleton2);
    }

在私有构造函数中添加限制处理,防止实例多次重复创建。

    private Singleton() {
        if (LazySingleton.INSTANCE != null) {
            throw new RuntimeException("非法访问");
        }
    }

序列化破坏

序列化是将一个创建好的对象写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。由于反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就会破坏单例

public class SeriableSingleton implements Serializable {
    public final static SeriableSingleton INSTANCE = new SeriableSingleton();

    private SeriableSingleton() {
    }

    public static SeriableSingleton getInstance() {
        return INSTANCE;
    }

//    private Object readResolve(){ return INSTANCE;}


    public static void main(String[] args) {

        SeriableSingleton s1 = SeriableSingleton.getInstance();
        SeriableSingleton s2 = null;
        FileOutputStream fos = null;
        try {

            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s1);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s2 = (SeriableSingleton) ois.readObject();
            ois.close();

            System.out.println(s1 == s2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

只需要增加一个叫readResolve方法返回实例就可以解决单例模式被序列化破坏的问题。

关键核心在于ObjectInputStreamreadObject(Class<?> type)方法调用readObject0方法中的switch (tc) 判断处理。

注意:实际上还是会实例化多次,只不过新创建的对象没有被返回而已。如果创建对象的发生频率加快,则内存分配开销也会随之增大

private Object readResolve(){ return INSTANCE;}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136914.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!