java常用的几种单例模式(懒汉式、饿汉式、登记式)

简单的讲,单例模式就是确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
何时用到?线程池、缓存、日志对象、对话框、显卡驱动程序、打印机中都用到,spring中用的最多:Spring Context Factory用的是单例,bean默认都是单例,配置文件也是单例;有些全局性的东西也可以设置为单例。
在java中,java类加载器的先后顺序为:
  1. 从上往下(java的变量需要先声明才能使用)
  2. 先静态(静态块和static关键字修饰在实例化以前分配内存空间)后动态(对象实例化)
  3. 先属性后方法(成员变量不能定义在方法中,只能定义在class下)

懒汉式:

不着急实例化,需要用的时候才初始化。 
情况一:
/**
 * 懒汉式单例
 * 在第一次调用的时候实例化自己
 */
public class Singleton1 {
    //1、先将构造方法私有化
    private Singleton1() {
    }

    //2、然后声明一个静态变量保存单例的引用
    private static Singleton1 singleton1 = null;

    //3、通过提供一个静态方法来获得单例的引用
    public static Singleton1 getInstance() {  
        if (singleton1 == null) {
            singleton1 = new Singleton1();
        }
        return singleton1;
    }
}
//缺点:1、在反射面前没什么用,因为java反射机制是能够实例化构造方法为private的类的。2、线程不安全,因为线程访问具有随机性,并发环境可能出现多个singleton1实例。
情况二:
/**
 * 懒汉式单例
 * 保证线程安全
 */
public class Singleton2 {
    //1、先将构造方法私有化
    private Singleton2() {
    }

    //2、然后声明一个静态变量保存单例的引用
    private static Singleton2 singleton2 = null;

    //3、通过提供一个静态方法来获得单例的引用
    //为了保证多线程环境下正确访问,给方法加上同步锁synchronized
    public static synchronized Singleton2 getInstance() {
        if (singleton2 == null) {
            singleton2 = new Singleton2();
        }
        return singleton2;
    }
}
//缺点:在方法调用上加了同步,虽然线程安全了,但是每次都有同步,会影响性能,毕竟99%是不需要同步的
情况三:
/**
 * 懒汉式单例
 * 双重锁检查
 * 主要是为了解决线程安全问题
 */
public class Singleton3 {
    //1、先将构造方法私有化
    private Singleton3() {
    }

    //2、然后声明一个静态变量保存单例的引用
    private static Singleton3 singleton3 = null;

    //3、通过提供一个静态方法来获得单例的引用
    //为了保证多线程环境下的另一种实现方式,双重锁检查
    public static Singleton3 getInstance() {
        if (singleton3 == null) {
            //这个classLoader只能加载一次
            synchronized (Singleton3.class) {
                if (singleton3 == null) {
                    singleton3 = new Singleton3();
                }
            }
        }
        return singleton3;
    }
}
//缺点:在getInstance中做了两次null检查,确保了只有第一次调用单例时才会做同步,这样也是线程安全的,同时也避免了每次都同步的性能损耗。但是降低了程序响应速度和性能,主要是因为双重锁。
情况四:
/**
 * 懒汉式单例
 * 静态内部类
 */
public class Singleton4 {
    //1、将构造方法私有化
    private Singleton4() {
    }

    //2、先声明一个静态内部类
    //内部类的初始化需要依赖主类,需要先等主类实例化之后,内部类才能开始实例化
    private static class LazyHolder {
        //这里加final是为了防止内部将这个属性覆盖掉
        private static final Singleton4 INSTANCE = new Singleton4();
    }

    //3、 同样提供静态方法获取实例
    //这里加final是为了防止子类重写父类
    public static final Singleton4 getInstance() {
        return LazyHolder.INSTANCE;
    }
}
//这种方式是Singleton4类被装载了,instance不一定被初始化。因为LazyHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载LazyHolder类,从而实例化instance。
//如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton4类加载时就实例化,因为我不能确保Singleton4类还可能在其他地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。
//总结下这种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以这种是最好的。

饿汉式:

先实例化。
情况一:

/**
 * 饿汉式单例
 * 在类初始化时,已经自行实例化
 */
public class Singleton5 {
    //1、将默认构造方法私有化
    private Singleton5() {
    }

    //2、声明静态变量,在类实例化之前就初始化变量,将对象引用保存
    private static final Singleton5 singleton5 = new Singleton5();

    //3、开放静态方法,获取实例
    public static Singleton5 getInstance() {
        return singleton5;
    }
}
//缺点:如果这个类一直没用,就显得浪费资源了。        

注册登记式:

相当于有一个容器装载所有实例,在实例产生之前先检查下容器有没有,如果有就直接取出来,如果没有就先new一个放进去,然后给后面的人用,SpringIoc容器就是一种注册登记式单例。
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 注册登记式单例
 * 类似Spring里面的方法,将类名注册,下次从里面直接获取
 */
public class Singleton7 {
    //Spring最底层的这个容器就是一个map,说白了,IOC容器就是一个map
    private static Map<String, Singleton7> map = new ConcurrentHashMap<String, Singleton7>();

    //每个class对应一个map的key,也就是唯一的id
    static {
        Singleton7 singleton7 = new Singleton7();
        map.put(singleton7.getClass().getName(), singleton7);
    }

    //保护默认构造函数
    protected Singleton7() {
    }

    //静态工厂方法,返回此类唯一的实例
    public static Singleton7 getInstance(String name) {
        if (name == null) {
            name = Singleton7.class.getName();
        }
        if (map.get(name) == null) {
            try {
                map.put(name, (Singleton7) Class.forName(name).newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return map.get(name);
    }
}
//登记式单例实际上维护了一种单例类的实例,将这些实例存放在一个Map中,对于已经登记过的实例,则从Map直接返回,没有登记的,则先登记,然后返回。
//登记式单例内部实现其实还是用的饿汉式,因为其中的static方法块,它的的单例在类被装载时就被实例化了。

以下为不常用的单例:

枚举式单例

/**
 * 枚举式单例
 */
public enum Singleton6 {
    INSTANCE;

    public void getInstance() {
    }

}
//这种方式不仅能多线程同步问题,而且能防止反序列化重新创建新的对象,不过由于jdk1.5中才加入enum特性,所以不常用

懒汉式和饿汉式的区别

饿汉就是类一旦被加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,而懒汉比较懒,只有当调用getInstance的时候,才会去初始化这个单例。
可以从以下两点区分:

1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,懒汉式本身是非线程安全的,为了实现线程安全,上面也有几种方法。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会用这个单例,都会占据一定的内存,在第一次调用时速度也会更快,因为其资源已经初始化完成。而懒汉式会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的比较多,性能上会有所延迟,之后就和饿汉式一样了。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值