单例模式(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() {  
    }  
}

经验之谈:一般情况下,不建议使用 懒汉方式,建议使用  (登记式/静态内部类),如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用 双检锁方式。

 

posted on 2024-04-14 14:40  zy平平仄仄  阅读(53)  评论(0编辑  收藏  举报