前言
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。 在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
单例类型
- 懒汉式:在真正需要使用对象时才去创建该单例类对象(时间换空间)
- 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用(空间换时间)
懒汉模式(线程不安全)
public class Singleton_01 {
private static Singleton_01 instance;
private Singleton_01(){
}
public static Singleton_01 getInstance(){
if(instance == null){
instance = new Singleton_01();
}
return instance;
}
}
这种方式在并发情况下会创建出多个实例,从而没有达到单例的要求。
懒汉模式(线程安全)
public class Singleton_02 {
private static Singleton_02 instance;
private Singleton_02(){
}
public synchronized static Singleton_02 getInstance(){
if(instance == null){
instance = new Singleton_02();
}
return instance;
}
}
这种方式虽然解决了线程安全问题,但是锁太重了会大大降低系统的性能。
饿汉模式(线程安全)
public class Singleton_03 {
private static Singleton_03 instance = new Singleton_03();
private Singleton_03(){
}
public static Singleton_03 getInstance(){
return instance;
}
}
饿汉模式是线程安全的,在系统启动时就会加载,后续有外部需要使用的时候获取即可。这样做的问题是即使不使用也会创建出来占用系统资源。
使用内部类(线程安全)
public class Singleton_04 {
private Singleton_04(){
}
private static class SingletonHolder{
private static Singleton_04 instance = new Singleton_04();
}
public static Singleton_04 getInstance(){
return SingletonHolder.instance;
}
}
使用类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加锁的方式耗费性能。这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载。
双重锁检查(线程安全)
public class Singleton_05 {
private static volatile Singleton_05 instance;
private Singleton_05(){
}
public static Singleton_05 getInstance(){
if(null != instance){
return instance;
}
synchronized (Singleton_05.class){
if (null == instance){
instance = new Singleton_05();
}
}
return instance;
}
}
双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时。
单例枚举(线程安全)
public enum Singleton_06 {
INSTANCE;
private DBConnection connection = null;
private Singleton_06(){
connection = new DBConnection();
}
public DBConnection getConnection(){
return connection;
}
}
public static void main(String[] args) {
DBConnection connection = Singleton_06.INSTANCE.getConnection();
}
Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。也就是说,序列化的时候只将DATASOURCE这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。