目录
一、什么是单例模式?
相信大家在面试过程中被提到单例模式的次数不少。
单例模式只是众多程序设计模式中最常用的一种,其它的还有工厂模式、建造模式、策略模式等。
单例模式,指的是一个类中的对象只能有一个,它在内存中只会创建一次对象的设计模式。
在程序中如果多次用到同一个类中的方法进行操作时,在使用时就会创建多个对象。为了防止频繁创建对象造成内存资源浪费,就可以使用单例模式。
可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
二、单例模式的类型
单例模式有两种类型:
- 饿汉式:在类加载的时候就已经创建好该类的对象了,不会存在并发安全和性能问题。
- 懒汉式:顾名思义,它比较懒。只有在真正需要使用对象的时候才去创建该类的对象。好处是可以节约内存资源,但是需要解决多线程情况下的安全问题。
三、单例模式的公共特征
- 单例类的对象只有一个
- 私有构造器
- 单例类必须自己创建自己的对象
- 提供公共的静态方法,为其它所有类提供这个单例对象
四、单例模式-饿汉式
饿汉式在类加载时就创建好了该类对象,在程序调用时就能直接返回单例对象
public class Singleton1 {
//提供私有的构造方法,这样外面类在new对象的时候就不能创建对象了
private Singleton1(){};
//保证每次提供的对象是同一个
//一进入内存就会被创建一个对象
private static Singleton1 singleton1 = new Singleton1();
//提供公共获取Singleton1对象的方法
public static Singleton1 getSingleton(){
return singleton1;
}
}
五、单例模式-懒汉式
5.1 懒汉式实现方式一(有问题不提倡使用)
存在线程安全的问题,多线程下无法保证对象的唯一性!
懒汉式代码:
public class Singleton2 {
private Singleton2(){};
//进入内存先不进行创建对象
private static Singleton2 singleton2 = null;
public static Singleton2 getSingleton2(){
if(singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
测试代码:
public class SingletonTest{
@Test
public void getSingleton2Test(){
//如果在多线程情况下,这种单例模式无法保证对象的唯一性
for (int i = 0; i < 10; i++) {
new Thread(()->{
Singleton2 singleton2 = Singleton2.getSingleton2();
System.out.println(singleton2);
}).start();
}
}
}
结果:出现了两个对象
5.2 懒汉式实现方式二(提倡使用)
上面那种懒汉式实现会出现线程安全问题,所以现在就需要解决。
使用DCL(Double Check Lock )双检查判断加同步锁的写法,还需要添加:
- 加上synchronize同步代码块
- 加上volatile关键字,作用:
- 保证线程之间内存的可见性(如果当前线程做了修改,后面的线程获取的则是修改后最新的数据)
- 禁止CPU指令重排,保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变化(如果发生指令重排,在创建对象的时候可能还没创建成功就给返回了一个空对象,出现空指针异常)
代码:
public class Singleton3 {
private Singleton3(){};
private volatile static Singleton3 singleton3 = null;
public static Singleton3 getSingleton3(){
//提升效率,如果对象不为空,则说明以及创建好了一个对象,直接返回就行不用每次都加锁判断
if (singleton3 == null){
//把创建对象的过程加入同步锁,防止出现线程安全问题
synchronized (Singleton3.class){
//再次检查对象是否存在,如果不存在才真正的创建对象
if (singleton3 == null) {
singleton3 = new Singleton3();
}
}
}
return singleton3;
}
}
测试:
public class SingletonTest{
@Test
public void getSingleton3Test(){
//在多线程情况下,能保证对象的唯一性
for (int i = 0; i < 10; i++) {
new Thread(()->{
Singleton3 singleton3 = Singleton3.getSingleton3();
System.out.println(singleton3);
}).start();
}
}
}
效果: