设计模式之单例模式

Posted ProChick

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之单例模式相关的知识,希望对你有一定的参考价值。

一.简要概述

  • 单例模式就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个获取该实例的静态方法。
  • 单例模式确保了某一个类只有一个实例,而且会自行实例化并向整个系统提供这个实例,保证了自身唯一性。

二.模式结构

通常使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现

三.实现方式

1.饿汉式实现

线程安全、调用效率高、不能延时加载、有可能造成资源浪费

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        System.out.println(s1 == s2);
    }
}

// 实现一
class Singleton{
    private final static Singleton instance = new Singleton();
    
    private Singleton(){
        
    }
    
    public static Singleton getInstance(){
        return this.instance;
    }
}

// 实现二
class Singleton{
    private final static Singleton instance;
    
    static{
        instance = new Singleton();
    }
    
    private Singleton(){
        
    }
    
    public static Singleton getInstance(){
        return this.instance;
    }
}

2.懒汉式实现

单线程安全但在多线程情况下不安全、调用效率不高、可以延时加载,能够节约资源

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        System.out.println(s1 == s2);
    }
}

// 线程不安全
public class Singleton {
    private static Singleton instance;

    private Singleton() {
        
    }

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

// 线程安全
public class Singleton {
    private static Singleton instance;

    private Singleton() {
        
    }

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

3.双重检测锁实现

线程安全、调用效率高、可以延时加载,能够节约资源

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        System.out.println(s1 == s2);
    }
}

public class Singleton {

    private static volatile Singleton instance;

    private Singleton() {
        
    }

    public static Singleton getInstance() {
        // 先判断instance是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

【注】:由于在JVM中可能出现指令重排的现象,也就是在多线程环境下,有可能一个对象被分配了地址空间但还没有进行初始化,所以就有可能创建多个对象。而volatile关键字能够解决指令重排的问题,保证了变量值只要发生变化就会立即同步到主存。

4.静态内部类实现

线程安全、调用效率高、可以延时加载、能够节约资源

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        System.out.println(s1 == s2);
    }
}

public class Singleton {

    private Singleton() {
        
    }

    private static class InnerSingleton {
        private final static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return InnerSingleton.instance;
    }
}

5.枚举类实现

线程安全、调用效率高、不能延时加载、有可能造成资源浪费

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.INSTANCE();
        Singleton s2 = Singleton.INSTANCE();
        
        System.out.println(s1 == s2);
    }
}

public enum Singleton {

   	INSTANCE;

    private String name;
    
    public String getName {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

【注】:这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反射或反序列化手段破坏单例性。

四.注意事项

  • 对于上述实现方式的前四种而言,我们其实可以通过反射或者反序列化的手段来破坏这种单例性。

    通过反射进行破坏

    public class TestSingleton{
        public static void main(String[] args){
    		Constructor<?> constructor = Singleton.class.getDeclaredConstructor();
    		constructor.setAccessible(true);
            
            Singleton s1 = constructor.newInstance();
            Singleton s2 = constructor.newInstance();
            
            System.out.println(s1 == s2);
        }
    }
    
    class Singleton{
        private final static Singleton instance = new Singleton();
        
        private Singleton(){
            
        }
        
        public static Singleton getInstance(){
            return this.instance;
        }
    }
    

    通过反序列化进行破坏

    public class TestSingleton{
        public static void main(String[] args){
            Singleton s1 = Singleton.getInstance();
            
    		FileOutputStream fOutputStream = new FileOutputStream("序列化文件位置");
    		ObjectOutputStream outputStream = new ObjectOutputStream(fOutputStream);
    		outputStream.writeObject(s1);
    		
            FileInputStream fInputStream = new FileInputStream("序列化文件位置");
    		ObjectInputStream inputStream = new ObjectInputStream(fInputStream);
    		Singleton s2= (Singleton)inputStream.readObject();
    
            System.out.println(s1 == s2);
        }
    }
    
    class Singleton{
        private final static Singleton instance = new Singleton();
        
        private Singleton(){
            
        }
        
        public static Singleton getInstance(){
            return this.instance;
        }
    }
    
  • 对于枚举实现的方式而言,其自身就是单例模式,而且由于JVM提供的安全保证,其不会受反射或者反序列化的影响。

五.优点好处

  • 由于单例模式只生成一个实例,所以减少了系统性能开销,尤其是在生成大对象的时候
  • 由于单例模式在系统中设置了全局访问点,所以优化了共享资源访问

六.如何选用

  • 当所创建的对象所占用的资源比较少时,而且不看重延迟性,则选用饿汉式或枚举类
  • 当所创建的对象所占用的资源比较多时,而且看重延迟性,则选用懒汉式或静态内部类或双重检测锁

七.应用场景

  • 应用程序只需要一个该类的实例对象
  • 客户调用某个实例只允许使用一个访问点

比如JDK源码中的Runtime类

以上是关于设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之单例模式

Java设计模式之单例模式

设计模式之单例模式以及简单代码实现

设计模式之单例设计模式

设计模式之单例模式

设计模式之单例模式