单例模式(Singleton Pattern)
是 Java 中最简单的设计模式之一,这种类型的设计模式属于创建型模式。目的是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这样做可以节省系统资源,并且保证某些类在系统中只存在一个实例。
主要解决:一个全局使用的类频繁地创建与销毁。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的(private关键字)
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
类型:单例模式可以分为几种类型,包括 饿汉式,懒汉式,登记式 。饿汉式单例在类加载时就创建实例,而懒汉式单例则在首次使用时创建实例。这两种实现方式都需要考虑线程安全问题。
多线程情况下: 双重检查锁定(double-checked locking)是一种常用的实现线程安全单例的方法。
======================================================== 以上八股文 来源 网上乱查的 ================================================================================================
举个简单小例子: 在使用数据库时, 首先要获取 jdbc 链接, 然后进行增删改查操作, 每次 增加操作 ,删除操作 ,查询 和修改操作时, 都要获取 jdbc 链接。
那么这个时候, 只保存一个 jdbc链接 在系统中, 每次操作数据库时 使用创建好 的JDBC 链接 就不需要每次操作 都创建了
代码:
/** * 饿汉式 */ public class SingletonJDBC { private static SingletonJDBC instance = new SingletonJDBC(); private SingletonJDBC(){} public static SingletonJDBC getInstance(){ return instance; } public void JdbcMessage(){ System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX "); } }
public static void main(String[] args) {
//SingletonJDBC jdbc = new SingletonJDBC();
SingletonJDBC singletonJDBC = SingletonJDBC.getInstance();
singletonJDBC.JdbcMessage();
}
标红地方会报如下错误,private
以上代码 使用了static 关键字(static 当Java虚拟机(JVM)加载类时,就会执行该代码块), 加载的时候就将 jdbc 给 创建好了, 可是, 在这个时候不操作数据库,那么就不应该 加载, 在使用的时候再加载(懒汉式出现 lazy loading)
代码:
/** * 懒汉汉式 lazy loading */ public class SingletonJDBC { private static SingletonJDBC instance; private SingletonJDBC(){} public static SingletonJDBC getInstance(){ if (null == instance){ instance = new SingletonJDBC(); } return instance; } public void JdbcMessage(){ System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX "); } }
出现问题了, 在使用数据库的时候,多个地方 同时(多线程) 需要jdbc 链接 ,那么当第一个 使用者 来的时候, 走 到了 null == instance 的时候, 第二个使用者来了, 也是空, 那么 它们创建的 就不是一个 相同的 SingletonJDBC 了 !!
验证一下
private static SingletonJDBC instance; private SingletonJDBC(){} public static SingletonJDBC getInstance(){ if (null == instance){ try { Thread.sleep(10); }catch (Exception e){ e.printStackTrace(); } instance = new SingletonJDBC(); } return instance; } public void JdbcMessage(){ System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX "); } } public static void main(String[] args) { for (int i = 0; i < 1000; i++) { new Thread(() -> { SingletonJDBC singletonJDBC = SingletonJDBC.getInstance(); singletonJDBC.JdbcMessage(); System.out.println(singletonJDBC.hashCode()); }).start();
}
}
结果:
这些 hashCode 不对, 不是一个 (又增加了多余的开销,如果是业务中的唯一数据, 这不就出问题了么)
修改一下 加个 synchronized,代码如下
public static synchronized SingletonJDBC getInstance(){ if (null == instance){ try { Thread.sleep(10); }catch (Exception e){ e.printStackTrace(); } instance = new SingletonJDBC(); } return instance; }
结果:完美解决问题 (但是, 加了synchronized 是锁住了 SingletonJDBC 整个 对象, 每次过来 要判断 锁 的情况, 效率又低了【如果是上千万数据交换】)
修改一下 (双重检查),代码如下 ( volatile 关键字 )
public class SingletonJDBC {
private static volatile SingletonJDBC instance;
private SingletonJDBC() {
}
public static SingletonJDBC getInstance() {
if (null == instance) {
synchronized (SingletonJDBC.class) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
if (null == instance) {
instance = new SingletonJDBC();
}
}
}
return instance;
}
public void JdbcMessage() {
System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX ");
}
}
代码 复杂,效果不是很理想,修改一下, (登记式/静态内部类)
/** * 登记式/静态内部类 */ public class SingletonJDBC { private SingletonJDBC() { } private static class SingletonJDBCHolder { private static final SingletonJDBC INSTANCE = new SingletonJDBC(); } public static SingletonJDBC getInstance() { return SingletonJDBCHolder.INSTANCE; } public void JdbcMessage() { System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX "); } }
说明: 在类加载 时候, 是不会 加载静态内部类的 , 只有当调用 getInstance 方法时候,会显式装载 SingletonJDBCHolder 。这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程。
结合上篇文章 , (工厂模式 + 单例模式) 可以简单的设计一个 数据库链接复用代码
=============================================================== 收 工====================================================================
补充 以下内容来源 ==============================菜鸟教程=================================
枚举
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。
实例
public enum Singleton { INSTANCE; public void whateverMethod() { } }
经验之谈:一般情况下,不建议使用 懒汉方式,建议使用 (登记式/静态内部类),如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用 双检锁方式。