转载自自己的博客 https://www.cnblogs.com/zhaokexin/p/9968078.html
大家好,博主是个东北人哈尔滨那嘎达的,在实习中吧,感觉自己学习之路贼拉长,学习又没乐趣,感觉工作中的人都感觉我都东北话好玩儿,
那我就用东北话来分享一些知识和学习成果,这样既进步又有趣,以下有什么不对的欢迎大家呲哒我。
在我们写代码的时候总会遇到抛异常啥的,但是那玩意究竟咋用,往哪嘎达用,我就简简单单分享一下子。
单例设计模式的7种写法
咱们在面试中面试官总会问我们会什么设计模式,一般我们会说几种常见的常用的设计模式:工厂方法模式、单例模式、建造者模式、原型模式
代理........一大堆
其实说的多不如会的精几个,下面我说一下我对单例的了解,也欢迎大家指正
首先我们知道单例分为懒汉和饿汉,
第一种(懒汉,线程不安全)不建议用:
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉,一看我们就能知道字面意思很懒,人家调用getInstance才实例化,但是如果多线程就不安全了,当多个线程同时调用getInstance时候,可能两条线程同时判断是null
那么就可能出两个或者两个以上的实例,就违背了单例的原则,那么我们怎么能让懒汉也安全呢?
第二种(懒汉,线程安全)同步方法不建议用:
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
我们可以看出我们加了synchronized同步方法来控制线程安全,当一条线程执行getInstance()其他线程被阻塞,所以保证一个时刻只有一个线程访问,
但是带来致命缺点就是会效率太低了。
第三种(饿汉,线程安全)
//对象是方法被调用时,才初始化,也叫做对象的延时加载。称为:懒汉式。
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton () { }
public static Singleton getInstance() {
return instance;
}
}
代码可以看出,讲构造函数私有化,并且在类中创建一个本类对象,在类中写一个方法获取到本类对象。
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例的话,则会造成内存的浪费。
第四种静态代码块(饿汉,线程安全)
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,
就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
第五种双重检查(可以用)
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
Double-Check概念对于多线程开发来说不会陌生,就像代码中一张,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。有人问怎么保证安全的呢?其实我们实例化代码执行一次后,后面再次访问时,判断第一个if (singleton == null)的时候因为不是null,所以不用走同步的 synchronized (Singleton.class) 直接return实例化对象。就可以了
(2019/3/1增加volatile作用)
volatile是为了防止指令重排
,而不是为了可见性,为什么要保证Singleton
变量的指令重排呢?
我们两个线程thread1和thread2同时调用getInstance()中的singleton = new Singleton()不是一个原子操作,所以对于cpu层面会有三次操作这一行代码,所以会这三次操作:
- 给 singleton 分配内存
- 调用 Singleton 的构造函数来初始化成员变量,形成实例
- 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
这里的关键在于——线程thread1对instance的写操作没有完成,线程thread2就执行了读操作。
这种方法线程安全;延迟加载;效率较高。
第六种静态内部类(可以用)
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.instance ;
}
}
其实这种和饿汉式有点一样又不咋像,都是用类装载的机制来保证初始化实例时只有一个线程,但是饿汉式加载就实例化了,没有懒加载,这种静态内部类就是在类加载不回实例化
需要实例的时候调用getInstance,然后为我们返回一个内部类实例的Singleton。
忘记说第七种了,其实就是一种懒汉,像第二种同步方法,这种就同步代码块,都是效率低,不建议使用的。
打卡2018/11/16 博主是个实习的小白,每天积累一点,欢迎大家指正评论,一起加油!