JUC(14)单列模式

饿汉式

饿汉模式,可以想象一个很饿的人,需要立马吃东西,饿汉模式便是这样,在类加载时就创建对象,由于在类加载时就创建单例,因此不存在线程安全问题

//饿汉式
public class SingletonDemo1 {
    //私有化构造器
    private SingletonDemo1() {

    }
    //类初始化时立即加载该对象
    private static SingletonDemo1 instance = new SingletonDemo1();

    //提供公共的获取方法,由于静态的instance在类加载时就创建,因此不存在线程安全问题
    public static SingletonDemo1 getInstance() {
        return instance;
    }
}

//测试
class SingletonDemo1Test {
    public static void main(String[] args) {
        SingletonDemo1 instance = SingletonDemo1.getInstance();
        SingletonDemo1 instance1 = SingletonDemo1.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

但饿汉式也存在一定的问题,即如果在该类里面存在大量开辟空间的语句,如很多数组或集合,但又不马上使用他们,这时这样的单例模式会消耗大量的内存,影响性能

懒汉式

顾名思义,懒汉式,就是懒,即在类加载时并不会立马创建单例对象,而是只生成一个单例的引用,即可以延时加载

//懒汉模式
public class SingletonDemo2 {
    //私有化构造器
    private SingletonDemo2() {

    }

    //只提供一个实例,并不创建对象
    private static SingletonDemo2 instance;

    //提供公共的获取方法,因为不是在类加载时就创建对象,因此存在线程安全问题,使用synchronized关键字保证线程安全,效率降低
    public static SingletonDemo2 getInstance() {
        if (instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

//测试
class SingletonDemo2Test {
    public static void main(String[] args) {
        SingletonDemo2 instance = SingletonDemo2.getInstance();
        SingletonDemo2 instance1 = SingletonDemo2.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

多线程则会产生不同对象,不适用。

DCL 懒汉式

//DCL懒汉式(双重检测锁模式)
public class SingletonDemo3 {
    //私有化构造器
    private SingletonDemo3() {

    }
    private static volatile SingletonDemo3 instance;

    public static SingletonDemo3 getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo3.class) {
                if (instance == null) {
                    instance = new SingletonDemo3();
                }
            }
        }
        return instance;
    }
}

//测试
class SingletonDemo3Test {
    public static void main(String[] args) {
        SingletonDemo3 instance = SingletonDemo3.getInstance();
        SingletonDemo3 instance1 = SingletonDemo3.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

实际可以解决多线程,但是理论上是不能的。

因为单列创建对象分为三步:1. 分配内存空间2、执行构造方法,初始化对象3、把这个对象指向这个空间

线程A 因为指令重排 1-> 3->2 。还没有执行到2的时候,线程b在使用单列的时候发现空间被占用了,以为单列被创建,会获得一个没有构造过的对象,出现问题。

静态内部类

使用静态内部类解决了线程安全问题,并实现了延时加载

//静态内部类实现
public class SingletonDemo4 {
    private SingletonDemo4() {

    }

    //不会在外部类初始化时就直接加载,只有当调用了getInstance方法时才会静态加载,线程安全,final保证了在内存中只有一份
    private static class InnerClass{
        private static final SingletonDemo4 instance = new SingletonDemo4();
    }

    public static SingletonDemo4 getInstance() {
        return InnerClass.instance;
    }
}

//测试
class SingletonDemo4Test {
    public static void main(String[] args) {
        SingletonDemo4 instance = SingletonDemo4.getInstance();
        SingletonDemo4 instance1 = SingletonDemo4.getInstance();
        System.out.println(instance == instance1); //输出true
    }
}

但是通过反射可以破环单列

class SingletonDemo4Test {
    public static void main(String[] args) throws Exception{
        SingletonDemo4 instance = SingletonDemo4.getInstance();
        SingletonDemo4 instance1 = SingletonDemo4.getInstance();
        System.out.println(instance == instance1); //true
        
        Constructor<SingletonDemo4> declaredConstructor = SingletonDemo4.class.getDeclaredConstructor();
        //关闭权限检测
        declaredConstructor.setAccessible(true);
        SingletonDemo4 instance2 = declaredConstructor.newInstance();
        System.out.println(instance == instance2); //false
    }
}

枚举

public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
  public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, In vocationTargetException, InstantiationException {
      EnumSingle instance1 = EnumSingle.INSTANCE;
      Constructor<EnumSingle> declaredConstructor =
        EnumSingle.class.getDeclaredConstructor(String.class,int.class);
      declaredConstructor.setAccessible(true);
     EnumSingle instance2 = declaredConstructor.newInstance();
    //报错 
     System.out.println(instance1);
     System.out.println(instance2);
}
}

通过反射发现不能 破坏单列。

六、五种实现单例模式的方式的对比

饿汉式:线程安全(不排除反射),调用效率高,不能延时加载
懒汉式:线程安全(不排除反射),调用效率不高,可以延时加载
DCL懒汉式:由于JVM底层模型原因,偶尔出现问题,不建议使用
静态内部类式:线程安全(不排除反射),调用效率高,可以延时加载
枚举单例:线程安全,调用效率高,不能延时加载

七、单例模式常见场景

Windows的任务管理器、回收站等
servlet中每个servlet都是单例
数据库连接池一般都是单例的
Spring中每个Bean都是单例的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值