首先是饿汉式(非延迟加载单例类)
public class HungrySinglePatterns {
/**
* 类进行初始化的时候,就立即加载这个对象。没有延时加载的优势。加载类时,线程是安全的。
*/
private static HungrySinglePatterns instance = new HungrySinglePatterns();
private HungrySinglePatterns() { }
/**
* 方法没有同步,调用率高
*/
public static HungrySinglePatterns getInstance() {
return instance;
}
}
应用:待补充
2.懒汉式(同步延迟加载)
public class LazyLoadSinglePatterns {
private static LazyLoadSinglePatterns instance;
/**
* 私有化构造器
*/
private LazyLoadSinglePatterns() { }
/**
* 因为是延时加载所以有可能出现线程同步的问题。所以要加上 同步块。
* 如果A线程执行 方法。 instance为null的时候, B线程又要执行方法,那么这个时候,两个线程都判断instance为null
* 就会创建出两个对象。 所以要加上synchronized。 导致调用效率不高。
* @return
*/
public static synchronized LazyLoadSinglePatterns getInstance() {
if (instance == null) {
return new LazyLoadSinglePatterns();
}
return instance;
}
}
应用:待补充
3.双重检测锁
/**
* 双重检测锁
* 懒加载,调用率高。没有同步问题。但是因为jvm底层问题,容易出现问题,不推荐使用
*/
public class DoubuCheckLockedSinglePatterns {
private volatile static DoubuCheckLockedSinglePatterns instance = null;
private DoubuCheckLockedSinglePatterns() {}
public static DoubuCheckLockedSinglePatterns getInstance() {
if (instance == null) { // 1. 第一次检查
synchronized (DoubuCheckLockedSinglePatterns.class) { // 2
if (instance == null) { // 3. 第二次检查
instance = new DoubuCheckLockedSinglePatterns(); // 4
}
}
}
return instance;
}
}
双重检测锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“重排序”,这也是失败的一个主要原因。所以这里要加volatile才能成功。
我们假设有两个线程 a 和 b 调用 getInstance() 方法,假设 a 先走,一路走到 4 这一步,执行 instance = new Singleton() 这句代码。
instance = new Singleton() 这句代码首先会申请一段空间,然后将各个属性初始化为零值(0/null),执行构造方法中的属性赋值[1],将这个对象的引用赋值给 instance[2]。在这个过程中,[1] 和 [2] 可能会发生重排序。
此时,线程 b 刚刚进来执行到 1(看上面的代码块),就有可能会看到 instance 不为 null,然后线程 b 也就不会等待监视器锁,而是直接返回 instance。问题是这个 instance 可能还没执行完构造方法(线程 a 此时还在 4 这一步),所以线程 b 拿到的 instance 是不完整的,它里面的属性值可能是初始化的零值(0/false/null),而不是线程 a 在构造方法中指定的值。
应用:
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
Spring的依赖注入(包括lazy-init方式)都是发生在 AbstractBeanFactory 的 getBean 里。 getBean 的 doGetBean 方法调用 getSingleton 进行bean的创建。lazy-init方式(lazy-init=“true”),在用户向容器第一次索要bean时进行调用;非lazy-init方式(lazy-init=“false”),在容器初始化时候进行调用。
从上面代码可以看到,spring依赖注入时,使用了双重检测锁的单例模式。也许是现在的jvm不会有双重检测锁失败的问题了?并没有查到相关资料,希望有大佬补充。
4.静态内部类
/**
* 静态内部类,实现懒加载单例模式
* 线程安全,懒加载,并且实现了延时加载。调用效率高
*/
public class StaticInteriorSinglePatterns {
private static class SingletonClassInstance{
private static final StaticInteriorSinglePatterns instance = new StaticInteriorSinglePatterns();
}
private StaticInteriorSinglePatterns() {}
public static StaticInteriorSinglePatterns getInstance() {
return StaticInteriorSinglePatterns.SingletonClassInstance.instance;
}
}
5.枚举类
这里我用了一个获取数据库连接的例子
public enum DataSourceEnum {
/**
* mysql的Connection
*/
DATASOURCE();
private Connection connection = null;
private DataSourceEnum() {
try {
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost/youDatabase?useUnicode=true&characterEncoding=utf-8";
String user="root";
String password="root";
connection = DriverManager.getConnection(url,user,password);
} catch (Exception e) {
e.printStackTrace();
}
}
public Connection getConnection() {
return connection;
}
}