为什么使用单例模式
单例最有代表就是我们耳熟能详的 windows 任务管理器,无论我们创建多少次,总是打开一个窗口。
- 如果打开多个重复的窗口,那就是对资源的浪费,资源是宝贵的。
- 多个窗口还要保证一致性,否则谁也不知道谁对谁错。
实现
单例模式又可以分为预加载和懒加载。当然我们在生产环境中一般使用较多的是 线程安全+懒加载。
预加载
/**
* 预加载
*/
public class PreloadSingleton {
public static PreloadSingleton instance = new PreloadSingleton();
/**
* 其他的类无法实例化单例类的对象
*/
private PreloadSingleton(){
}
public static PreloadSingleton getInstance(){
return instance;
}
}
上面这种写法可以保证线程安全。
但是对象还没有使用时就被创建,很明显这种写法会造成资源浪费。
懒加载
/**
* 懒加载 单例模式
*/
public class LazyLoadingSingleton {
private static LazyLoadingSingleton instance = null;
private LazyLoadingSingleton(){}
public static LazyLoadingSingleton getInstance() {
if (instance == null){
instance = new LazyLoadingSingleton();
}
return instance;
}
}
懒加载显然是不能保证线程安全的,在Java实例化对象会分为三步,JVM为了提高程序执行性能,会对没有依赖关系的代码进行重排序。
- 初始化内存空间。
- 初始化对象。
- 设置instance实例指向刚分配的内存空间。
懒加载+线程安全
synchronized 关键字是我们处理线程安全的一个利器,加载 getInstace() 函数可以保证线程安全。但是,如果要经常的调用 getInstance() 方法,不管有没有初始化实例,都会唤醒和阻塞线程。为了避免线程的上下文切换消耗大量时间,如果对象已经实例化了,我们没有必要再使用 synchronized 加锁,直接返回对象。
synchronized
把锁加在 if (instance == null) 里面,保证instance未实例化的时候才加锁。
/**
* synchronized + 懒加载
*/
public class SynchronizedSingleton {
private static SynchronizedSingleton instatce = null;
private SynchronizedSingleton(){}
public static synchronized SynchronizedSingleton getInstance(){
if (instatce == null){
synchronized (SynchronizedSingleton.class) {
if (instatce == null) {
instatce = new SynchronizedSingleton();
}
}
}
return instatce;
}
}
synchronized+volatile
Java 在 new 一个对象是无法保证顺序性的。因此需要另一个关键字Volatile保证对象实例化过程中的顺序性。
/**
* 单例模式,线程安全 synchronized+volatile
*/
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance() {
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}