为什么要有单例模式
在一个项目里,可能有一些对象是专门用来保存一些数据的,这个对象可能非常大。
可能占用内存几十个G,或者几百G,或者上千个G。
如果这个对象不小心再被创建一次,服务器的内存可能就被占用光了。
所以说,保证这个对象只有一个是一件非常重要的事。
饿汉和懒汉的意思
这里的饿汉和懒汉跟我们平时听到的并不是同一个意思。
饿汉表示急切
,急迫
的意思。
懒汉表示不急迫
,从容
的意思。
饿汉式实现
这种实现中是先把对象准备好的(比较急迫),所以就叫成饿汉式(Eager)。
- 先创建对象
- 将构造器私有化,防止类外创建对象
- 暴露一个获取对象的方法
public class SingletonEager {
private static SingletonEager instance = new SingletonEager();
private SingletonEager(){
};
public static SingletonEager getInstance() {
return instance;
}
}
- 在多线程的情况下,饿汉式实现不会涉及到变量的修改操作,所以是线程安全的。
饿汉式测试:
public class Test {
public static void main(String[] args) {
SingletonEager instance = SingletonEager.getInstance();
SingletonEager instance2 = SingletonEager.getInstance();
System.out.println(instance == instance2);
}
}
输出:
懒汉式实现
懒汉式(Lazy)
跟饿汉式不同,当调用获取对象的方法时,才会创建一个对象。
代码:
public class SingletonLazy {
public static SingletonLazy obj = null;
private SingletonLazy(){
};
public static SingletonLazy getInstance() {
if (obj == null) {
obj = new SingletonLazy();
}
return obj;
}
}
但是这不是线程安全的,因为当多个线程同时调用getInstance
方法的时候,就有可能创建无数多个对象。
下图以两个线程为例子:
- 上图就表示了,多个线程执行这段代码的时候就有可能创建多个对象,所以要上锁。在这里要对整个
if代码块
上锁。
代码如下:
public static SingletonLazy getInstance() {
synchronized(lock) {
if (obj == null) {
obj = new SingletonLazy();
}
}
return obj;
}
- 但是呢,如果有很多线程同时调用这个方法,而且当
obj != null
时,对于上面的代码会导致很多线程都阻塞了。 - 然而
obj != null
时并不会执行创建对象的操作,只执行return obj
,此时线程是安全的,所以再进行一次判断。 - 只有当
obj == null
的时候才执行synchronized(lock){....}
。
代码:
public static SingletonLazy getInstance() {
if (obj == null) {
synchronized(lock) {
if (obj == null) {
obj = new SingletonLazy();
}
}
}
return obj;
}
测试懒汉式:
public class Test2 {
public static void main(String[] args) {
SingletonLazy instance = SingletonLazy.getInstance();
SingletonLazy instance2 = SingletonLazy.getInstance();
System.out.println(instance == instance2);
}
}
结果:
关于单例模式的实现方式并不止懒汉式
和饿汉式
这两种,其他的暂时不做拓展,懒汉式
和饿汉式
是很经典的两种实现方式,先把这两种掌握,之后遇到其他的实现方式再做拓展。