设计模式-单例

一、单例 Singleton 

在设计模式之前引出设计模式的的分类。图片出处https://blog.csdn.net/weixin_40550118/article/details/107209866

  

1 饿汉式,类加载到内存后,就实例化一个单例,用JVM保证线程安全,不论是否用到都会初始化。

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();//static 保证class被load到内存之后只加载一次,final保证此类不允许改变

    private Singleton() {};//私有构造保证类不能被外部new出来

    public static Singleton getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        Singleton m1 = Singleton.getInstance();
        Singleton m2 = Singleton.getInstance();
        System.out.println(m1 == m2);
    }
}

2.饿汉式的变种方式,同样是static加载方式

public class Singleton {
    private static final Singleton INSTANCE;
    static {
        INSTANCE = new Singleton();
    }

    private Singleton() {};

    public static Singleton getInstance() {
        return INSTANCE;
    }
    
    public static void main(String[] args) {
        Singleton m1 = Singleton.getInstance();
        Singleton m2 = Singleton.getInstance();
        System.out.println(m1 == m2);
    }
}

3.懒汉式 用到时候才初始化,但多线程情况下会导致创建出来的对象不是同一个对象的问题。

public class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (INSTANCE == null) { //此处不能保证线程安全
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

4. 懒汉式 加锁解决线程安全问题,但是每次都要加锁,性能有损耗。

public class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {//简单加锁不可行
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
    
}

5.缩小加锁范围,但是还是没能解决多线程问题

public class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {//多线程情况下可能会有多个线程进行初始化。
            //妄图通过减小同步代码块的方式提高效率,然后不可行
            synchronized (Singleton.class) {
                INSTANCE = new Singleton();
            }
        }
        return INSTANCE;
    }
}

6.双重校验锁进行判断防止频繁加锁带来性能影响

public class Singleton {
    private static volatile Singleton INSTANCE;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            //双重检查
            synchronized (Singleton.class) {
                if(INSTANCE == null) {
                   INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

7.静态内部类方式,比第一种方式更完美,只有在调用getInstance方法才会创建Singleton的实例。也是由JVM保证只创建一次。

public class Singleton {

    private Singleton() {
    }

    //Singleton 的构造方法是私有的,但是在内部类可以初始化
    private static class SingletonHolder {
        private final static Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;//返回静态内部类初始化的对象
    }
}

综上所述,1-6-7方式可用。

以上方式都会被反序列化进行破坏,因为普通的Java类的反序列化过程中,会通过反射调用类的默认构造函数来初始化对象。所以,即使单例中构造函数是私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以这就破坏了单例。

8.Enum方式

public enum Singleton {

    INSTANCE;

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Singleton.INSTANCE.hashCode());
            }).start();
        }
    }

}

(以下内容引自https://blog.csdn.net/qq_41486137/article/details/102959453

Joshua Bloch大神的《Effective Java》书中提到使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。并且没有以上方式的线程安全问题。

因为定义枚举时使用enum和class一样,是Java中的一个关键字。就像class对应用一个Class类一样,enum也对应有一个Enum类。

通过将定义好的枚举反编译,我们就能发现,其实枚举在经过javac的编译之后,会被转换成形如public final class T extends Enum的定义。

而且,枚举中的各个枚举项同时通过static来定义的。如:

public enum T {
SPRING,SUMMER,AUTUMN,WINTER;
}
反编译后代码为:

public final class T extends Enum
{
//省略部分内容
public static final T SPRING;
public static final T SUMMER;
public static final T AUTUMN;
public static final T WINTER;
private static final T ENUMKaTeX parse error: Expected '}', got 'EOF' at end of input: …); ENUMVALUES = (new T[] {
SPRING, SUMMER, AUTUMN, WINTER
});
}
}
了解JVM的类加载机制的朋友应该对这部分比较清楚。static类型的属性会在类被加载之后被初始化,我们在深度分析Java的ClassLoader机制(源码级别)中介绍过,

当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的(因为虚拟机在加载枚举的类的时候,会使用ClassLoader的loadClass方法,

而这个方法使用同步代码块保证了线程安全)。所以,创建一个enum类型是线程安全的。

也就是说,我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。而我们知道,解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题

所以,由于枚举的以上特性,枚举实现的单例是天生线程安全的。

另外,使用枚举也可以避免反射和反序列化破坏。因为在序列化方面,Java中有明确规定,枚举的序列化和反序列化是有特殊定制的。这就可以避免反序列化过程中由于反射而导致的单例被破坏问题。

单例模式的优点

  • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  • 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值