单例模式设计

单例模式

单例模式即创建的对象具有唯一性,通过方法获取的都是同一个对象。(将对象的创建和获取分离)

实现单例模式的方法主要分为:

饿汉式:类加载阶段创建对象。

懒汉式:使用时创建对象。

下面是饿汉式创建的几种方式:

1.静态成员变量方式

class Dog{

    private Dog(){}

    private static final Dog dog =new Dog();

    public static Dog dog(){
        return  dog;
    }
}

2.静态代码块方式

class Dog4{

    private static  Dog4 dog4;

    private Dog4(){}
    
    static {
        dog4=new Dog4();
    }


    public static Dog4 dog4(){
        return  dog4;
    }
}

3.枚举方式(无线程安全、序列化和反射破坏单例的问题)

enum Dog1{
    DOG_1;
}

下面是懒汉式创建的几种方式:

1.线程安全的实现方式

class Dog2 {
    private static Dog2 dog2 = null;

    private Dog2() {}

    public static Dog2 dog2() {
        synchronized (Dog2.class) {
            if (dog2 == null) {
                return dog2 = new Dog2();
            }
        }
        return dog2;
    }

}

2.dcl方式

详解dcl问题:

为什么需要利用双重检查?

因为线程安全的单例实现方式,会造成在第1次并发至第n次并发中,线程都会在synchronized进行阻塞等待,影响性能;而dcl的方式可以使得只需要在第1次并发时在synchronized进行阻塞等待,后续次数的并发无需再进行阻塞等待,提高了系统性能。

为什么需要在单例对象上添加volatile关键字?

从dog3=new Dog3()进行分析,对象的创建总共分为6个步骤:1.检查类是否加载到方法区,未加载进行类加载;2.在堆内存中分配内存空间,有两种分配方式,分为指针碰撞(标记-压缩算法)和空闲列表(标记-清除算法)的方式;3.处理并发问题(每个线程分配一个TLAB)4.默认初始化对象信息;5.对象头信息设置;6.执行init方法,显示初始化;7.赋值引用变量。在上述中,6和7的步骤是划分开的,在程序执行过程中可能会发生指令重排序,即赋值操作会先于初始化对象信息执行。

在dcl中,多线程环境下,由于上述的指令重排序问题可能会导致程序运行出错。若A线程获取到synchronized的锁,那么编译器优化导致dog3=new Dog3()重排序,赋值指令先于初始化对象指令执行。在A线程完成赋值操作时,B线程在进行第一个if判断,由于A线程已经对变量进行了赋值,因此,B线程直接返回了该引用变量(该变量所指对象是一个“空壳”对象),在对象初始化任务量繁杂的情况下,将造成B线程接下来使用该变量时出错。

class Dog3{
    private static volatile Dog3 dog3=null;

    private Dog3(){}
    
    public static Dog3 dog3(){
        if(dog3==null){
            synchronized (Dog3.class){
                if(dog3==null){
                    return dog3=new Dog3();
                }
            }
        }
        return dog3;
    }
}

3.静态内部类方式

class Dog5{
    
    private Dog5(){}
    
    private static class DogDemo{
      private static final Dog5 dog5=new Dog5();
   }

   public static Dog5 dog5(){
        return DogDemo.dog5;
   }
}

解决序列化和反射破坏单例的问题(以静态内部类实现方式为例)

1.实现readResolve方法解决序列化破坏单例的问题

2.使用构造器抛异常的方式解决反射破坏单例的问题

class Dog5{

    //解决反射破坏单例
    private boolean flag=false;
    private Dog5(){
        synchronized (Dog5.class){
            if(flag){
                throw  new RuntimeException("不能创建多个对象");
            }
            flag=true;
        }
    }
    
    private static class DogDemo{
      private static final Dog5 dog5=new Dog5();
   }

   public static Dog5 dog5(){
        return DogDemo.dog5;
   }

   //解决序列化破坏单例问题
    public Object readResolve(){
        return DogDemo.dog5;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值