单例模式特点:
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)