单例模式(Singleton)
单例模式是对象的创建模式,单例模式能够确保某个类只有一个单一的实例对象存在,同时能够自行实例化并将单一的实例提供给外界调用的特点,其在实际项目开发中经常被用到。
特点:
1. 单例类(Singleton)只能有一个唯一的实例存在。
2. 单例类必须有能够自行创建自己的实例对象的能力。
3. 单例类必须能够给外界其他对象提供这里实例。
问题1:
首先我们需要来考虑2件事情,既然单例模式需要保证系统中最多只有一个这样的对象事例,那么我怎么才能确保只能有一个实例呢??
构造方法:如果构造方法是 public,那么任何类都可以 new 我的对象,不能保证单例,所以单例模式构造方法一定是 private的
如果构造方法是 private的,那么我怎么 new对象呢?说明我只能在我自己的类中,先new出自己,然后提供给外部。
由于系统只有一个单例对象,那么线程同步是一定需要的,怎么来做呢?
就是因为上面的第2步,我们就出现了很多单例模式的写法,下面我们一一介绍:
饿汉式
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; } }
**问题二:
1:为什么 构造方法是 private的? 因为上面提到的,不能由其他类任意new单例模式的类
2:为什么 getInstance() 是 static方法,因为用了 类名.来调用getInstance() 方法。
3:为什么 instance 是 static的? 静态方法可以访问非静态变量吗?显然不行,所以instance也是static的,而且静态变量是不是只会被初始化一次(感觉3个问题都是java考试题一样,哈哈哈!)
4:上面的第二种使用到了静态初始化块,在Singleton第一次被使用的时候初始化。**
懒汉式
public class Singleton {
private static Singleton instance;private Singleton(){} public static Singleton getInstance() { if(null == instance){ instance = new Singleton(); } return instance; } }
结论:与饿汉式的不同:不是一看到 instance 就初始化,懒汉要等到第一次使用的时候才初始化,不像饿汉一样一见到 instance 就初始化,这也被称为 懒加载,如果系统中很多这样的类,显然是懒加载的时候效率更高
线程安全:
如果对线程学得还行的同学就应该知道,上面的饱汉式写法其实不是那么的安全!!虽然在 getInstance() 中有非空判断,但是线程这个东西是由时间片控制的,如果一个线程刚刚非空判断通过了,然后切换到下个线程,也执行并通过非空判断,并切换到上一个线程,其实两个线程都会执行new操作,就不单例了!(虽然发生概率不高)但是我们为了更加的准确,我们来看看线程安全的版本怎么写
public class Singleton {
private static Singleton instance;private Singleton(){} public static synchronized Singleton getInstance() { if(null == instance){ instance = new Singleton(); } return instance; } }
结论: 只是在 getInstance() 前添加了 synchronized
关键字,保证同一时刻只能有一个线程执行获取到类锁,并执行创建实例代码,保证了只会创建一次。同时也有了懒加载的特性。但是但是, 由于是单例模式,如果将锁直接锁在 static
的方法上(相当于类锁,其他static方法也不能执行),那么效率是非常低的,所以我们还有一定的改进。
双重检查加锁
public class Singleton {
private static volatile Singleton instance;private Singleton() { } public static Singleton getInstance() { if (null == instance) { synchronized (Singleton.class) { if (null == instance) { instance = new Singleton(); } } } return instance; } }
懒加载:
确保线程安全 只有第一次创建类的时候可能发生阻塞,后面由于非空判断都不会阻塞
volatile是什么鬼?如果你关注线程安全的话就会了解,volatile是用来保证多个线程并发时,访问的都是内存中的同一个volatile对象。枚举单例
public enum Singleton {
INSTANCE; }
What the fuck? 就3行,好吧,我也是刚刚学的,解释一下原理,枚举类型也是在第一次被使用的时候初始化,并且默认构造函数是private修饰,而且线程安全。
序列化问题
传统单例写法,如果实现了序列化接口,他们就不能再保持单例,因为readObject() 方法每次会返回新的对象,所以你需要重写这个方法:
private Object readResolve(){
return INSTANCE; }
好啦,单例模式就介绍到这里,如果还没看懂就自个动手编码哈~