Java/Swift 单例模式的多种写法

32 篇文章 0 订阅
4 篇文章 0 订阅
本文探讨了单例模式的特点,强调了在Java和Swift中实现单例模式时需要注意的陷阱,如防止反序列化、反射创建多个实例。详细介绍了Java中的单例模式实现,包括存在问题的写法、加锁优化以及静态内部类的懒加载解决方案。同时,展示了Swift3.0中静态类型的线程安全单例实现,并提到了变量名与关键字冲突的解决方式。
摘要由CSDN通过智能技术生成

单例模式特点:

1、 一个进程只能有一个实例;

2、不能有派生类;

3、对于插件化app, 单例模式有坑。 不同插件可能都实例化出一个单例对象, 因为classloader不同。

 

      依据Java语言特性, 要防止反序列化和反射创建多个实例的漏洞。

      设计模式的书本上一般是这样写:

 

         public final class SingleTon {
                  private static SingleTon sInstance;     
                  public static SingleTon getInstance() {
                            if (sInstance == null) {
                                     sInstance = new SingleTon();
                            }
                            return sInstance;
                   }
        } public final class SingleTon {
                  private static SingleTon sInstance;     
                  public static SingleTon getInstance() {
                            if (sInstance == null) {
                                     sInstance = new SingleTon();
                            }
                            return sInstance;
                   }
        }

 

       仔细看这几行代码, 能发现什么问题吗?  脑洞大开一下。 

 

        1. 在外部类中可以new出SingleTon对象。 原理:如果不写构造函数,编译器会生成一个无参数的构造函数;如果写了构造函数,则编译器不会生成构造函数。

        2. 能反射出多个实例。

        3. 多线程时怎么办? 可能生成多个SingleTon实例, 我们想到了锁。 

 

         public final class SingleTon {
                  private static SingleTon sInstance;   
                  private SingleTon() {
                         //将构造函数声明成私有函数, 从而在类外部无法实例化该类
                  }  
                  public synchronized static SingleTon getInstance() {
                            if (sInstance == null) {
                                     sInstance = new SingleTon();
                            }
                            return sInstance;
                   }
        }

 

        添加synchronized关键字后实际上是SingleTon.class上锁, 但对函数上锁的颗粒度较大, 影响多线程并发的性能, 实际上我们只需要对new对象上锁, 读对象时不上锁。 更好的写法是

 

 

 

 public final class SingleTon {
                  private volatile static SingleTon sInstance;     
                  public static SingleTon getInstance() {
                            if (sInstance != null) {
                                    return sInstance;
                            }
                            synchronized(SingleTon.class) {
                           if (sInstance == null) {
                                     sInstance = new SingleTon();
                           }
                            }
                            return sInstance;
                   }
        }

       上面的写法有坑! 不写构造函数时,会有一个默认的构造函数, 可以new出实例。 对于单例模式来说,必须设置构造函数为私有的。

 

       如果不使用锁, 能不能实现单例呢?  答案是可以的, 肯定要借助静态内部类了。该方式是懒加载,调用getInstance函数时才会创建SingleTon实例, 所以性能比饿汉式要好。

 

    public final class SingleTon {
        private SingleTon() {
            if (getInstance() != null) {
                throw new RuntimeException();  //防止反射创建多个对象的漏洞  
            }
        }

        private static class Inner {
            static final SingleTon sInstance = new SingleTon();  //私有静态类, 只会执行一次new;使用final关键字是为了避免被反射篡改。
        }

        public static SingleTon getInstance() {
            return Inner.sInstance;
        }
    }

     原理: ClassLoader的loadClass方法是线程安全的,保证一个class只能被加载一次。 在加载类时会实例化静态变量, 所以静态变量是线程安全的。

 

         删除类私有构造方法里的判断逻辑, 可以通过反射的方式创建多个不同的实例, 所以必须要在构造函数里做判断!!!

 

public class Main {
    public static final class SingleTon {
        private SingleTon() {

        }

        private static class Inner {
            static final SingleTon sInstance = new SingleTon();  //私有静态类, 只会执行一次new
        }

        public static SingleTon getInstance() {
            return Inner.sInstance;
        }
    }

    public static void main(String[] args) throws Exception {
        try {
            Class clz = Class.forName("com.brycegao.test.Main$SingleTon");
            Constructor constructor = clz.getDeclaredConstructor();
            constructor.setAccessible(true);

            Object obj1 = constructor.newInstance();  
            Object obj2 = constructor.newInstance();
            System.out.println(obj1 + "," + obj2);    //反射创建出2个不同的实例
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

结果:com.brycegao.test.Main$SingleTon@610455d6,com.brycegao.test.Main$SingleTon@511d50c0

 

            

 

Swift3.0单例模式的2个写法如下, 虽然没有同步关键字, 但苹果已经说了加上static关键字后多线程时也能保证是一个实例。  

        注意要声明init函数, 而且是private访问类型, 从而只能通过类的静态参数得到实例。

 

苹果的说明:In Swift, you can simply use a static type property, which is guaranteed to be lazily initialized only once, even when accessed across multiple threads simultaneously

 

final class SingleTon {
    //单例类的属性
    var name: String
    var age: Int
    
    private init() {
        name = ""
        age = 0
        print("SingleTon init is called")
    }
    
    static let sInstance = SingleTon()
}

final class SingleTon2 {
    //单例类的属性
    var name: String
    var age: Int
    
    private init() {
        name = ""
        age = 0
        print("SingleTon init is called")
    }
    
    //使用闭包的GET语法, 类似于lazy参数, 在运行时赋初值
    static var sInstance: SingleTon2 {
        let instance = SingleTon2()
        instance.name = "zhangsan"
        instance.age = 20
        
        return instance
    }
}

 

   在Swift语言中default是关键字, 如果想用default作为变量名, 可以转义。 PS: Swift的其它关键字也可以这样转义!!!

 

 

class SomeClass {
    var param = 1
    
    public static let `default` = SomeClass()
}

print(SomeClass.default.param)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值