设计模式之单例模式
学习路上,还是得不断的接收新的知识。因为大多数知识都是共通的,这不,学着学着还是得绕到设计模式上,就从最简单的单例模式开始学习吧。基本的模式就有23种,我想写成一个系列的文章,但现在的我肯定是还没有这个能力的。要深刻理解设计模式,最好的方式应该 就是去应用它吧,相信等以后慢慢积累沉淀,我一定会把这个系列写完的==。
1.设计模式是什么
设计模式,其实就是一种设计理念,无关乎语言,使用它可以做到代码复用,保证代码的可靠性。但我现在压根也理解不到它有多大作用。emmm,但慢慢积累就好,毕竟谁也不能一蹴而就,好书也都值得我们去看第二遍第三遍。
就跟阿里巴巴的孤尽说的,学习设计模式的最高境界就是扫地僧,完全忘记设计模式,但写出来的都是设计模式。
2.单例模式
确保一个类只有一个实例,并提供一个全局访问点。
一般使用一个私有构造函数,一个私有静态变量,一个共有的静态函数实现。私有的构造函数保证不能通过构造函数来创建对象实例,只能通过公有的静态函数来返回唯一的私有静态变量。
3.单例模式的实现
(1)饿汉式——线程安全
public class Singleton {
private Singleton() {}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
因为使用的static修饰唯一的私有变量,所以不管怎么样,获取到的都是这一个单例。
public class Test {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
输出
true
缺点:不管你是否要使用该实例,在类加载的时候,都会进行实例化(这应该也是饿汉这个名字的由来吧哈哈),这在一定程度上就浪费了资源。
(2)懒汉式——线程不安全
public class Singleton {
private Singleton() {}
private static Singleton instance = null;
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
这样做的好处是延迟实例化,在没有使用到该类的时候,就不会实例化instance,从而节约资源。缺点是线程不安全,如果有多个线程同时进入if(instance == null),那么就会多次实例化instance。
(3)懒汉模式—–线程安全
public class Singleton {
private Singleton() {}
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
通过给getInstance方法加一个锁synchronized ,就能保证在一个时间点只能有一个线程进入该方法,从而避免了多次重复实例化的问题。但当一个线程进入的时候,其他线程必须等待,因此在性能上有一定的损耗。
(4)双重校验锁—–线程安全
public class Singleton {
private Singleton() {}
private volatile static Singleton instance = null;
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
其实我们现在只是要保证instance只被初始化一次就可以使用,所以在instance为null的时候,才给它加锁。如果没有最里层的if判断,当两个线程都进入之后,还是会被初始化两次,只是时间问题,所以最里层的if也是需要的。
我们给静态变量加了volatile 进行修饰,它可以禁止JVM的指令重排。instance = new Singleton();这条语句其实分三步执行:
分配内存空间
初始化对象
- 将对象指向分配的内存空间
但JVM有时为了优化,会自动进行指令重排,变成1->3->2这样的顺序,在这种情况下,如果是多线程,那么第二个线程获取到的,可能就是一个没有被初始化的对象。所以使用volatile修饰,可以保证在多线程情况下正常运行。
第一篇的总结就到这里啦。