单例模式
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象,一个最好的办法就是,让类自身负责保存它的唯一实例,这个类可以保证没有其他实例可以被创建。并且它可以提供一个访问该实例的方法。
单例模式使用场景:
需要频繁的进行创建和销毁对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到对象、工具类对象、频繁访问数据库或文件的对象(比如数据源,session工厂)。
单例模式的五种方式:
1、饿汉式
静态常量
1 2 3 4 5 6 7 8 9 10 11
| class SingLeton1{ private SingLeton1(){} private static SingLeton1 instance = new SingLeton1();
public static SingLeton1 getInstance(){ return instance; } }
|
测试:
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { SingLeton1 singLeton1 = SingLeton1.getInstance(); SingLeton1 singLeton2 = SingLeton1.getInstance();
System.out.println(singLeton1.hashCode() == singLeton2.hashCode());
}
|
静态代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class SingLeton2{
private SingLeton2(){}
private static SingLeton2 insance;
static { insance = new SingLeton2(); }
public static SingLeton2 getInstance(){ return insance; } }
|
测试
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { SingLeton2 singLeton1 = SingLeton2.getInstance(); SingLeton2 singLeton2 = SingLeton2.getInstance();
System.out.println(singLeton1.hashCode() == singLeton2.hashCode());
}
|
静态代码块方法和静态常量方法其实类似,只不过静态代码块方法将类实例化的过程放在了静态代码块中,也是在类加载的时候,就执行静态代码块中的代码。
分析:
饿汉式的方式在初始化时就创建好了对象,不管你之后是否用到,都会创建以一个实例。
因此它没有线程安全的问题,但是浪费内存空间
2、懒汉式
线程不安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Singleton1{
private Singleton1(){}
private static Singleton1 singLeton1;
public static Singleton1 getInstance(){ if(singLeton1 == null){ singLeton1 = new Singleton1(); }
return singLeton1; }
}
|
测试
1 2 3 4 5 6
| public static void main(String[] args) { Singleton1 singleton1 = Singleton1.getInstance(); Singleton1 singleton2 = Singleton1.getInstance();
System.out.println(singleton1.hashCode() == singleton2.hashCode()); }
|
只能在单线程下使用,
如果在多线程下,一个线程进入了if(singleton == null) 判断语句块中,还未来得及去创建实例对象,另一个线程也通过了这个判断语句,这是便会产生多个实例。所以在多线程环境下不可使用这种方式。
线程安全,同步方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Singleton2{ private Singleton2(){}
private static Singleton2 singleton2;
public static synchronized Singleton2 getInstance(){ if(singleton2 == null){ singleton2 = new Singleton2(); }
return singleton2; }
}
|
测试
1 2 3 4 5 6
| public static void main(String[] args) { Singleton2 singleton1 = Singleton2.getInstance(); Singleton2 singleton2 = Singleton2.getInstance();
System.out.println(singleton1.hashCode() == singleton2.hashCode()); }
|
这种方式解决了线程不安全问题,但效率太低了,每个线程在想获取类的实例时,都要进行同步。
3、双重检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Singleton{ private Singleton(){} private static volatile Singleton singleton;
public static Singleton getInstance(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } }
return singleton; } }
|
测试:
1 2 3 4 5 6 7 8
| public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1);
}
|
即保证了线程安全,有比直接上锁提高了执行效率,还节省了内存空间
4、静态内部类
1 2 3 4 5 6 7 8 9 10 11 12
| class Singleton{ private Singleton(){}
private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance(){ return SingletonInstance.INSTANCE; }
}
|
测试
1 2 3 4 5 6 7 8
| public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1.hashCode() == singleton2.hashCode());
}
|
这种方式采用了类加载的机制来保证初始化时只有一个线程。
静态内部类方式在Singleton类被加载时并不会立即实例化,而是在需要实例化时,调用getInstance()方法,才会加载SingletonInstance类,从而完成Singleton的实例化。
5、枚举
1 2 3
| enum Singleton{ INSTANCE }
|
测试
1 2 3 4 5 6 7
| public static void main(String[] args) { Singleton instance1 = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1.hashCode() == instance2.hashCode());
}
|
枚举能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。