单例模式
译:保证整个系统中一个类只有一个对象实例,实现这种功能的方式叫做单例模式。
白话文:在每次操作中,有且只有一个对象的实例在运行,这种模式可以很方便的共享这个对象资源。
单例模式分类(饿汉式、懒汉式、静态内部类)
单例模式的四大原则:
1:构造私有
2:以静态方法或者枚举返回实例
3:确保实例只有一个,尤其是在多线程的环境下
4:确保反序列化是不会重新构建对象
1:单例模式-饿汉式
个人理解:饿汉式就是在类创建的时候就一起创建好了,创建好之后就一直在资源堆里面,当饿了(当需要的时候)就可以很快的获取。(空间换时间)
Demo:
//单例模式-饿汉式
public class HungryChinese{
//在类加载时创建对象
private final static HungryChinese hungry = new HungryChinese();
//私有化构造器,防止外界创建对象(就是不能new HungryChinese())
private HungryChinese(){};
//提供外界获取对象的方法
public static HungryChinese getHungry (){
//返回单个实例(返回的始终是同一个实例)
return hungry;
}
}
优缺点:
优点:实例在加载时就创建了,所以可以很方便的获取
缺点:当饿汉式模式下实例数量太多的话,因不是每个实例都时时用到,所以会造成资源浪费,优点不完美咯~
2:单例模式-懒汉式
个人理解:区别于饿汉式不会在一开始就创建,当需要了的时候再创建,然后创建好之后就跟懒汉式一样了,创建好之后就一直在资源堆里面(时间换空间)
Demo:
//单例模式-懒汉式
public class HungryChinese{
//在需要时创建
private final static HungryChinese hungry = null;
//私有化构造器,防止外界创建对象(就是不能new HungryChinese())
private HungryChinese(){};
//提供外界获取对象的方法
public static HungryChinese getHungry (){
//判断是否已经创建实例,有则返回单个实例(返回的始终是同一个实例)
if (hungry == null){
return new HungryChinese();
}
return hungry;
}
}
优缺点:
优点:区别于饿汉式,这样会减少对资源的占用,需要用时才创建
缺点:懒汉式面临一个问题,当高并发的时候很容易造成线程不安全,在这个时候可以在对外获取的方法上添加 synchronized 加上锁,但是会影响性能的问题~
Demo:
//单例模式-懒汉式
public class HungryChinese{
//在需要时创建
private final static HungryChinese hungry = null;
//私有化构造器,防止外界创建对象(就是不能new HungryChinese())
private HungryChinese(){};
//提供外界获取对象的方法
public synchronized HungryChinese getHungry (){
//判断是否已经创建实例,有则返回单个实例(返回的始终是同一个实例)
if (hungry == null){
return new HungryChinese();
}
return hungry;
}
}
3:单例模式-静态内部类
个人理解:把实例化放在内部类中,故而加载的时候不会去实例化,只有当要用到的时候再加载,跟饿汉式和懒汉式有区别,放在内部类内,只会被加载一次,线程也安全了,占用资源也小
Demo:
//单例模式-静态内部类
public class HungryChinese{
//私有化构造器,防止外界创建对象(就是不能new HungryChinese())
private HungryChinese(){};
//内部类
private static class Chinese{
public static HungryChinese hungry = new HungryChinese();
}
//提供外界获取对象的方法
public synchronized HungryChinese getHungry (){
//返回单个实例(返回的始终是同一个实例)
return Chinese.hungry;
}
}
优缺点:
优点:资源利用率高,自由执行内部类的时候才会被实例化,同时也可以执行其他类的静态方法。
缺点:静态内部类加载时需要些时间,第一次反应时间不够快
4:工作中常用的
个人理解:在工作中,用得比较多的是饿汉式,可以直接获取,如果对资源比较看重的情况下,可以使用静态内部类的方式。
5:扩展(下面一段是copy大神的,我也就理解了哈哈哈~)
类加载时机:JAVA虚拟机在有且仅有的5种场景下会对类进行初始化。
1.遇到new、getstatic、setstatic或者invokestatic这4个字节码指令时,对应的java代码场景为:new一个关键字或者一个实例化对象时、读取或设置一个静态字段时(final修饰、已在编译期把结果放入常量池的除外)、调用一个类的静态方法时。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行初始化,需要先调用其初始化方法进行初始化。
3.当初始化一个类时,如果其父类还未进行初始化,会先触发其父类的初始化。
4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。 5.当使用JDK1.7等动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
这5种情况被称为是类的主动引用,注意,这里《虚拟机规范》中使用的限定词是"有且仅有",那么,除此之外的所有引用类都不会对类进行初始化,称为被动引用。静态内部类就属于被动引用的行列。