认识Java单例模式

一、什么是单例模式

单例模式是程序设计中常用到的一种设计模式,主要是使用在:我们只需要一个类实现保存一个对象,不希望有更多的对象被实例化保存从而造成不必要的资源浪费,这时候我们用到的设计模式就叫单例模式。

二、单例模式的特点

1、单例模式只有一个实例instance
2.、构造方法必须private私有,不能被其他new(包括子类)

private Singleton(){}

3、有静态static的类变量保存单例对象

private static Singleton instance = new Singleton();

4、单例对象的获取方式,一般采用getInstance

public static Singleton getInstance(){
    return instance;
}

三、单例模式的Java代码简单实现

1、懒汉模式
所谓的懒汉模式(个人理解就是得到用到的时候再说),如果单例的实例已经被创建,则直接返回该实例,如果为null没有被创建则创建实例。

public class SingletonDemo {
    private static SingletonDemo instance;
    private SingletonDemo(){

    }
    public static SingletonDemo getInstance(){
        if(instance==null){
            //用到的时候才new实例化->懒汉
            instance=new SingletonDemo();
        }
        return instance;
    }
}

这种懒汉模式有很明显的lazy loading(延迟加载)问题。但是这里有个坑,如果多个访问者访问方法getInstance(),则会造成创建多个instance实例,这也就是没有考虑到多线程的安全的问题。为了解决线程不安全的问题,自然有加锁的线程安全的懒汉模式。

public class SingletonDemo {
    private static SingletonDemo instance;
    private SingletonDemo(){

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

但是我们都知道加了锁之后的效率会很低下,并发的情况下就显得不适用。
2、饿汉模式
饿汉模式,个人理解就是一上来就需要实例化,它没有lazy loading的效果,类加载时就实例化的结果是相当于多了个常驻的内存(不同于静态类方法),它的好处就是类记载即实例化可以避免线程安全的问题,但同时不能达到需要的时候用到的特定效果(不管用不用得到,实例化后的对象都存在),只有会造成一定的内存浪费,同时也没有lazy loading效果

public class SingletonDemo {
    //加载时就实例化,之后直接访问->饿汉。
    private static SingletonDemo instance=new SingletonDemo();
    private SingletonDemo(){

    }
    public static SingletonDemo getInstance(){
        return instance;
    }
}

为了解决用到的时候才实例化,而不是类记载即实例化同时考虑到线程安全问题,我们可以再一步优化使用静态类内部加载实现。
3、静态类内部加载
静态内部就是弥补饿汉模式的缺点(先加载类之后有需要时实例化对象),在内部类中加载类,之后调用内部类方法实现实例化对象。同时也实现了lazy loading机制。

public class SingletonDemo {
    private static class SingletonMethod{
        //这里虽然类已经被加载,但是还未立即实例化
        private static SingletonDemo instance=new SingletonDemo();
    }
    private SingletonDemo(){
    }
    public static SingletonDemo getInstance(){
        //调用内部类SinglteonMethod的方法之后才会被实例化
        return SingletonMethod.instance;
    }
}

4、双重校验锁
加了锁之后的懒汉模式似乎看起来解决了“线程安全”与“延迟加载”两个问题,但是加了锁之后大大降低了性能,所以利用双重检验锁可以解决这问题

    public class Singleton {  
        private static Singleton instance = null;  
        private Singleton(){}  
        public static Singleton getInstance() {  
            if (instance == null) {  
                synchronized (Singleton.class) {  //1处
                    if (instance == null) {//2处
                        instance = new Singleton();//3处  
                    }  
                }  
            }  
            return instance;  
        }  
    } 

下面来详细了解它是怎样的实现原理。
第一步:线程A访问getInstance()方法,由于单例没有被实例化,所以进入了1处锁定块。
第二步:线程B也访问getInstance()方法,同样由于单例没有被实例化,也同样进进入1处锁定块,想获取锁,但是已经被线程A获取。线程B在1处阻塞。
第三步:线程A继续进入2处,由于为null,就继续进入3处实例化对象instance。
第四步:线程A退出锁synchronized块同时return instance,此时单例已经被实例化。
第五步:此时线程B获得锁,判断instance是否被实例化,是否为null,而线程A已经实例化对象instance,所以此时跳出3处,直接返回被线程A实例化了的instance。

这种方式看似完美解决了问题,但是很遗憾由于java中无序写入(具体解释),该方法并不完美,而且可能会犯很大的错误。
注:关于Java的内存模型“无序写入”问题,我的理解就是对象实例化的时候,如Person p = new Person(),正常流程是:实例p被创造之后会继续执行初始化构造函数new Person(),最后将p指向分配的内存空间(p此时不再为null),从而完成初始化。但是期间可能p实例在构造函数初始化之前就已经不为null(而不是最后被指向空间),也就是实例化p指向内存空间与构造函数初始化两个步骤没有固定的执行顺序,这就是“无序”问题。

四、单例模式的优缺点及使场景

  • 优点:
    (1)只有一个对象,减少了内存的使用。
    (2)对外界提供了唯一的实例化方法接口,保持了其内部的完整性外部的简单性。
    (3)避免对共享资源多重占用,大大提高了效率。
    (4)可以满足特定的实例化情况。
    (5)相比较全局变量/静态类方法来说,使用单例模式可以lazy loading延迟初始化。

  • 缺点:
    (1)单例模式可扩展性差,这是由于并没有存在抽象层的东西让其扩展。
    (2)单例模式使用的时候如果不注意线程安全的问题,将会影响读/写共享数据。
    (3)在并行开发测试的时候,单例模式可能会影响测试。

  • 单例模式使用场景:
    (1)单例模式只允许创建一个对象,所以常常用于被共用资源的场景下,比如JDBC中多个访问者使用一个数据库连接池Connection连接。
    (2)频繁创建对象并且消耗的资源过多的情况下可以使用单例模式。
    (3)类似于网站计数器,都是采用单例模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值