单例模式
-
单例模式是一种非常常见的设计模式,我们有必要规范地用好单例模式。
-
单例模式是指,在整个系统中,该单例类只有一个实例对象并始终对外提供同一个实例对象。
单例模式常见的写法有两种,懒汉模式和饿汉模式
- 懒汉模式:
public class Singleton {
//在类加载的时候就创建了单例类的对象
private static final Singleton instance = new Singleton();
//单例模式的无参构造函数,防止外部创建单例类的对象,故为私有的
private Singleton() {
}
public static Singleton newInstance() {
//返回为一的单例对象
return instance;
}
}
- 懒汉模式
public class Singleton {
//将单例类的对象作为静态实例
private static Singleton instance = null;
//依然是私有的无参构造
private Singleton {
}
public static Singleton newInstance() {
//在需要的时候才去创建单例对象
//如果该对象已经创建,则反之之前创建的对象,不会重新创建
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 懒汉模式的单例模式写法有延迟加载的意思,单例类的实例只有在真正需要用到的时候才创建。反观饿汉模式,单例类的实例是作为该类的一个静态常量存在的,在类初始化时就加载了。
- 但是,懒汉模式还存在问题,没有考虑多线程下线程安全的问题:***可能会在多个线程并发调用newInstance()方法时,出现多个单例类的实例的问题,这显然是不被允许的。
- 因此,我们很容易想到用锁的互斥同步的性质实现线程安全,从而解决上述问题,如下:
public class Singleton {
private static Singleton instance = null;
private Singleton {
}
//使用synchronized为方法加锁
public static synchronized Singleton newInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 以上写法用重量锁的形式通过互斥同步,使得在执行new Singleton()时只能够一个线程,故能保持单例实例的全局唯一,但同样的,这样会成为系统性能的瓶颈。
- 为了解决上述问题,就有了双重检查锁定的方式,如下:
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
//第一次检测
if(instance == null) {
//加锁
synchronized(Singleton.class) {
//第二次检测
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- **注意!上述双检测写法是错误的!**由于指令重排优化,可能导致初始化单例对象和将该对象地址赋给instance字段的顺序与上面java代码中书写的顺序不同。
- 为了解决上述由于指令重排所导致的问题,我们很自然地想到了volatile关键字,如下:
public class Singleton {
//使用volatile修饰instance变量
private static volatile Singleton instance = null;
private Singleton {
}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 上述就是最终的正确的双检测带锁的单例模式的写法。
- 由于有重量级锁的存在,对性能还是有一定的影响的。故有时候也可采用以下写法:
public class Singleton {
//私有的静态内部类
private static class SingletonHolder {
//共有的静态字段,可以通过外部类调用
public static Singleton instance = new Singleton();
}
//单例类的私有无参构造
private Singleton() {
}
//静态方法
public static Singleton newInstance() {
return SingletonHolder.isntance;
}
}
- 第一次访问类中的静态字段时会触发类加载,并且同一个类只加载一次,类加载过程由类加载器负责加锁,从而保证线程安全。
以上就是对单例模式的总结,循序渐进,慢慢完善。