单例模式
什么是单例模式
单例模式就是只允许有一个实例存在,自己向外部提供方法获取实例。
一般获取对象都是通过new对象来完成,这样系统中肯定就不至一个实例。我们怎么来完成这种单例模式了,我们可以通过把构造方法给私有化,这样外部就无法直接通过new来获取这个对象,我们内部再提供一个 getInstance() 来获取对象。
单例模式的应用场景
- 需要生成唯一序列的环境。
- 要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 方便资源相互通信的环境。
单例模式的实现
下面就用Person类来完成代码实现
- 第一种,在类加载的时候就完成对象的实例化
public class Person1 {
private static Person1 person = new Person1();
private Person1(){};
public static Person1 getInstance() {
return person;
}
}
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
- 第二种,第一种的升级,在getInstance()再生成实例
public class Person2 {
private static Person2 person = null;
private Person2(){};
public static Person2 getInstance() {
if (person == null) person = new Person2();
return person;
}
}
这种方式在多线程的情况下会创建多个实例,所以有了第三种方式。
- 第三种,双检
public class Person3 {
private static Person3 person = null;
private Person3(){};
public static Person3 getInstance() {
if (person == null) {
synchronized (Person3.class) {
if (person == null) {
person = new Person3();
}
}
}
return person;
}
}
为什么需要两次检查,为什么一次不行了。
分别介绍两次检查的作用:
- 第一次是为了效率,假如有两个线程A和B,如果没有第一次检查,A得到锁,B来了但是得不到锁,那B就等待,等待A释放这个锁,当B的到锁后再去判断。那么其实B等待的这个时间是不必要的,因为A已经把实例构建好了,B一直等待A释放锁,然后得到锁后发现实例也创建好了。
- 第二次是为了单例,还是以A和B两个线程来说,如果没有第二次检查,A得到锁,B来了但是得不到锁,那B就等待,等待A释放这个锁。这里和第一次很像,但是这次没有第二次检查,B得到锁后并不知道A已经创建好了实例,那么B会自己再创建一个实例,这样就不是单例模式了。