设计模式 ----- 单件模式

设计模式 —– 单件模式

个人博客,想要搭建个人博客的可以进来看看: http://www.ioqian.top/


单件模式,确保一个类只有一个实例额,并提供一个安全的全局访问点

所有设计模式中最简单的一种,但是在软件开发中使用的很频繁

背景 , 在我们的程序设计过程中,经常会用到一个类的实现在整个程序中只有一个初始化一个实例,为什么会出现这种需求哪?
1.多个实例可能会出现在不同的线程中同时操作同一个设备,比如同时读写同一个配置文件,同时操作打印设备,都会产生数据的不确定性
2.实例化一个类的代价很大,没必要在每个类中都实例化一个新的,比如在javaJDK中Calendar的实现本身就只能通过单利模式实现

1.不同类的初始化耗时差别很大。测试实例化10000个Date类型和100000个String类型和1个Calendar的耗时来进行分析,为什么只有1个Calendar,因为java自身的实现Calendar自身就使用了单例模式,没办法去初始化100000个。

    public static void main(String[] args) {
        //初始化100000个Date类
        long start =  System.currentTimeMillis();
        List list = new ArrayList<>(100000);
        for(int i = 0 ; i < 100000 ; i++){
            list.add(new Date());
        }
        long end = System.currentTimeMillis();
        System.out.println("初始化100000个Date 耗时"+(end - start)+"ms");
        //初始化100000个String类
        start =  System.currentTimeMillis();
        List lists = new ArrayList<>(100000);
        for(int i = 0 ; i < 100000 ; i++){
            lists.add(new String(""));
        }
       end = System.currentTimeMillis();
        System.out.println("初始化100000个String 耗时"+(end - start)+"ms");
        //初始化一个Calendar类
        start = System.currentTimeMillis();
        Calendar cc = Calendar.getInstance();
        end = System.currentTimeMillis();
        System.out.println("初始化1个Calendar耗时"+(end - start)+"ms");
    }
//结果
初始化100000个Date 耗时16ms
初始化100000个String 耗时3ms
初始化Calendar耗时26ms

Process finished with exit code 0

分析,对应Date和String虽然我们看起来十万个类的差别才9ms,看起来微不足道,但是换一种说话,每初始化一个Date类的耗时是初始化一个String类的4倍,这个4倍的概念就很大了吧,在java中肯定存在实例化特别耗时的一个类,比如说最后的Calendar,初始化一次就耗时26ms是初始化一次String的八十多万倍。(简直是天文数字,有没有)。所以针对这些类的实现必须使用单例模式(也就是单件模式)

2.单例模式
单例模式有以下3个特点:

  • 1.只能有一个实例。
  • 2.必须自行创建这个实例。
  • 3.必须给其他对象提供这一实例。

初级版本的单例模式

public class PrimaryInstance {
    //在不需要的时候不需要初始化
    private static PrimaryInstance instance = null;
    //构造方法设为private,外部无法初始化
    private PrimaryInstance(){}
    //得到PrimaryInstance唯一的方法
    public static PrimaryInstance getInstance(){
        if(instance == null){               //语句a
            instance = new PrimaryInstance();
        }
        return instance;
    }
}

看起来很完美,实现了单利模式的三个特点,但是当在多线程中使用时,出现了问题,得到了2个实例(我是jvm,两个线程同时进入getInstance()方法,当线程1刚进入语句a返回true时,线程1时间片到了,线程2开始执行,线程2判断语句a也是true,线程2实例化一个实例然后返回实例1,又到了线程1的时间,线程1上次已经判断语句为true然后也实例化一个实例,悲剧了…),违背了只能有一个实例的特点,既然是多线程引起的那简单了,直接加上synchronized关键字

中级版本的单例模式

public class SecondInstance {
    //在不需要的时候不需要初始化
    private static SecondInstance instance = null;
    //构造方法设为private,外部无法初始化
    private SecondInstance(){}
    //得到PrimaryInstance唯一的方法
    public static synchronized SecondInstance getInstance(){
        if(instance == null){
            instance = new SecondInstance();
        }
        return instance;
    }
}

这个中级版本的代码确实解决了多线程错误的问题,但是这样付出的代价太大了,每次获取实例都需要同步,太耗费时间了,我们只需要在第一次执行getInstance()有同步操作就可以了。那我们应该怎么办?

  • 1.如果getInstance()的性能对程序不是很关键,就不修改了,但是如果getInstance()在多线程中频繁的使用,哪就是一个噩梦了
  • 2.在类初始化的过程中就进行实例的初始化
public class NowInstance {
    //在静态声明中进行初始化
    private static NowInstance instance = new NowInstance();
    private NowInstance(){}
    public static NowInstance getInstance(){
        return instance;
    }
}
  • 3.最好的选择,用双重检测加锁机制,把synchronized操作放在方法的代码块中,就是最终的单例模式版本

终极版本的单利模式

public class AdvancedInstance {
    private static AdvancedInstance instance = null;
    private AdvancedInstance(){}
    public static AdvancedInstance getInstance(){
        if(instance == null){
            //同步操作放在这里保证了只有第一次实例化对象才进行同步操作
            synchronized (AdvancedInstance.class){
                //进入同步代码区后,再检查一次,可以解决中级代码中遇到的问题
                if(instance == null){
                    instance = new AdvancedInstance();
                }
            }
        }
        return instance;
    }
}

注意,当存在多个类加载时,可能会出现多个实例,所以当同时使用了多个类加载器,又使用了单例模式请小心;解决办法就是自行指定类加载器,并指定为同一个类加载器

你可能感兴趣的:(java)