Java设计模式之单例模式 通俗易懂 超详细 【内含案例】

By | 2020年7月31日

单例模式

推荐 Java 常见面试题

什么是单例模式 ?

确保程序中一个类只能被实例化一次,实现这种功能就叫单例模式

单例模式的好处是什么 ?

  1. 方便控制对象
  2. 节省资源减少浪费

怎么实现单例模式 ?

  1. 构造私有化
  2. 调用静态方法返回实例
  3. 确保对象的实例只有一个

常见的单例模式有哪些 ?

  1. 饿汉式

把对象创建好,需要使用的时候直接用就行饥肠辘辘 非常着急

  1. 懒汉式
  • 由于饿汉式容易浪费资源,比如类里 有public static 修饰的一个方法 test(),即可不创建实例就可访问到
  • 不到万不得已不创建实例,什么时候用什么时候创建

懒汉式的缺点呢 ?

线程不安全 但是可以控制 如何控制,下面会讲

代码实现饿汉式

创建 Singleton.java

public class Singleton1 {

   
    public static final Singleton1 INSTANCE = new Singleton1();
    
    //构造方法私有化
    private Singleton1(){

    }
    
    //get方式获取对象
    public static Singleton1 getInstance(){
        return INSTANCE;
    }
}

创建 TestSingleton.java

public class TestSingleton{
    public static void main(String[] args) {
        Singleton1 st1 = Singleton1.INSTANCE;
        Singleton1 st2 = Singleton1.getInstance();
        //return true
        System.out.println(st1.hashCode() == st2.hashCode());
        //return true
        System.out.println(st1 == st2);
    }
}

代码实现懒汉式

创建 Singleton2.java

public class Singleton2 {

    private static Singleton2 INSTANCE;

    //构造方法私有化
    private Singleton2(){

    }

    //get方式获取对象
    public static Singleton2 getInstance(){
        if (INSTANCE == null) {
            INSTANCE = new Singleton2();
        }
        return INSTANCE;
    }
}

x修改 TestSingleton.java

Singleton2 a = Singleton2.getInstance();
Singleton2 b = Singleton2.getInstance();
//ture
System.out.println(a == b);

注意: 再有的情况下懒汉式线程不安全比如多线程

举例说明 线程不安全

修改 Singleton2.java

public static Singleton2 getInstance(){
    if (INSTANCE == null) {
        try {
            Thread.sleep((int) Math.random() * 100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        INSTANCE = new Singleton2();
    }
    return INSTANCE;
}

修改 TestSingleton.java

Callable<Singleton2> c = new Callable<Singleton2>() {
    @Override
    public Singleton2 call() throws Exception {
        return Singleton2.getInstance();
    }
};

//创建两个线程池
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Singleton2> f1 = es.submit(c);
Future<Singleton2> f2 = es.submit(c);

Singleton2 a = f1.get();
Singleton2 b = f2.get();
/**
 * 有可能两种情况
 * 1. 判断是否一致:false
 * 2. 判断是否一致:true
 */
System.out.println("判断是否一致:" + (a == b));

为什么会这样呢 ?

  • 因为第一个线程 if (INSTANCE == null) 进去之后触发 sleep() 线程休眠
  • 第二个线程就紧跟着 if (INSTANCE == null) 这时 INSTANCE 还是等于null,随后进入线程休眠
  • 这样他两个都创建了实例,一共创建两次所以导致线程不安全

怎么解决线程不安全 ?

解决 懒汉式 线程不安全的方法

  1. 使用 synchronized 同步锁
  2. 使用静态内部类
使用 synchronized 同步锁

修改 Singleton2.java

public static Singleton2 getInstance(){
    //提高效率 已经new过后不需要再等着资源
    if (INSTANCE == null) {
        //可以使用同步锁 完成线程安全
        synchronized (Singleton1.class) {
            if (INSTANCE == null) {
                try {
                    Thread.sleep((int) Math.random() * 100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Singleton2();
            }
        }
    }
    return INSTANCE;
}
  • 再次运行 TestSingleton.java 测试 就会返回 true
  • 但是还是有一个问题,第一个线程已经创建好了,第二个线程还需要再次进入 synchronized块等待资源的控制权.可以在外面加上 if 判断 INSTANCE == null 即可提高效率
使用静态内部类

因使用 synchronized 同步锁,代码看起来不雅观,所以可以使用静态内部类,达到线程安全

修改 Singleton2.java

public class Singleton2 {

    //构造私有化
    private Singleton2(){

    }

    //在内部类被加载时,才创建
    //静态内部类 不会随着外部类加载,初始化 它是一个独立的 当用这个类时才会加载初始化
    //在内部类加载和初始化,所以线程是安全的
    private static class Inner{
        private static final Singleton2 INSTANCE = new Singleton2();
    }

    //get 方式获取静态内部类的INSTANCE
    public static Singleton2 getInstance(){
        return Inner.INSTANCE;
    }
}

小结

并不能说明 懒汉式 要强于 饿汉式,可以根据项目需求,来使用其中一种模式
再次推荐 保你面试必过的 java面试题
本文就先说到这里,有问题欢迎留言讨论

Category: 未分类

发表评论

电子邮件地址不会被公开。 必填项已用*标注