意图
保证系统中一个类只有一个实例并且提供对外访问。
优点
由于在内存中只存在一个对象,故可以节约系统资源,对于一些需要频繁创建和销毁的的对象,单例模式无疑可以提高系统的性能。
缺点
由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。多用于web应用设计以及各种池的设计。
实现要点
构造函数私有化。类定义中含有一个该类的静态私有对象,以及一个暴露单例的静态方法。
饿汉模式
//饿汉模式
public class Singleton1 {
//类一加载,就创建对象,故称为饿汉模式
private static Singleton1 singleton = new Singleton1();
private Singleton1(){
System.out.println("生成了一个实例");
}
public static Singleton1 getInstance() {
return singleton;
}
}
将对象放在类成员变量中,这样就可以在加载时初始化实例
优点:线程安全(JVM保证在多个线程想要加载类时,只有一个线程能加载)。
缺点:创建一些暂时不需要使用的单例,耗费资源,启动速度慢。
懒汉模式
//懒汉模式
public class Singleton2 {
//不初始化,等到要用时再初始化,称为延迟加载
private static Singleton2 singleton2;
private Singleton2(){
System.out.println("生成了一个实例");
}
//有线程安全问题,需要用sychronized进行线程同步
public static synchronized Singleton2 getInstance() {
//线程不安全,因为有可能两个线程同时访问
//在第一个线程正在创建对象时,第二个线程也进入if然后创建对象
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
将创建的工作暴露在方法中
优点:延迟加载,资源利用率高
缺点:线程不安全
双重检测锁
为了实现线程安全可采用如下方式:
public class safeLazyInitialization {
private static safeLazyInitialization instance;
public synchronized static safeLazyInitialization getInstance() {
if (instance == null) {
instance = new safeLazyInitialization();
}
return instance;
}
}
上述方案是线程安全的,但是由于锁住了整个getInstance(),所以这种单例模式的效率是非常低下的。尽量缩小锁的范围。所以我们不锁整个方法,而是锁住方法中的部分代码。
再次改进代码
public class safeDoubleCheckLocking {
private static safeDoubleCheckLocking instance;
public static safeDoubleCheckLocking getInstance() {
if (instance == null) {
synchronized (safeDoubleCheckLocking.class) {
if (instance == null) {
instance = new safeDoubleCheckLocking();
}
}
}
return instance;
}
}
相比于前一种,这种实现明显效率更高,因为synchronized关键字外部有一层判断,只要实例被创建了,就不会再进入同步代码块,而前一种是每次调用方法都会进入同步代码。
为什么synchronized外部加了一次判断,内部还要加一次判断呢?
double-check,也就是常说的双重校验。假设线程A执行到了第6行(尚未执行)if (instance == null),此时instance依然等于null,而线程B可能已经进入了外层判断,而且被阻塞在synchronized这里。线程A继续执行完成对象的创建后释放锁,线程B获取锁进入同步代码块,如果没有第二次判断,线程B会直接创建对象。所以synchronized内也必须加一次判断。
线程不安全的根本原因就是instance = new safeDoubleCheckLocking()不是原子操作。而是分为三步完成
1、分配内存给这个对象
2、初始化这个对象
3、把instance变量指向初始化的对象
正常情况下按照1 -> 2 -> 3的顺序执行,但是2和3可能会发生重排序,执行顺序变成1 -> 3 -> 2。如果是1 -> 3 -> 2的顺序执行。线程A执行完3,此时对象尚未初始化,但是instance变量已经不为null,线程B执行到synchronized关键字外部的if判断时,就直接返回了。此时线程B拿到的是一个尚未初始化完成的对象,可能会造成安全隐患。所以这种实现方式是线程不安全的。要解决这个问题,也就是解决重排序的问题,应该想到了另一个关键字volatile。
再次改造代码:
public class safeDoubleCheckLocking {
private volatile static safeDoubleCheckLocking instance;
public static safeDoubleCheckLocking getInstance() {
if (instance == null) {
synchronized (safeDoubleCheckLocking.class) {
if (instance == null) {
instance = new safeDoubleCheckLocking();
}
}
}
return instance;
}
}
静态内部类实现
双重校验锁的实现方式涉及到的知识较多,所以相对来说,还有更加简便的方式,那就是利用静态内部类
public class staticInnerClass {
private staticInnerClass instance;
private staticInnerClass(){
}
public static staticInnerClass getInstance() {
return SingletonInstance.instance;
}
private static class SingletonInstance {
private static staticInnerClass instance = new staticInnerClass();
}
}
这种实现方式既满足懒加载,又满足线程安全,代码量还少,相对来说是一种比较优雅的实现方式。
枚举方式
class Singleton{}
public enum A{
private Singleton instance;
A(){
instance=new Singleton();
}
public Singleton getInstance(){
return instance;
}
}
优点:实现简单,避免反射和序列化的漏洞