1 Star 0 Fork 165

ElonChung / Java-Review

forked from flatfish / Java-Review 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
创建型模式-单例设计模式.md 10.87 KB
一键复制 编辑 原始数据 按行查看 历史
icanci 提交于 2020-09-07 22:12 . :fire:更新文件名

创建型模式 - 单例设计模式

简介

  • 所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类 只能存在一个对象实例,并且该类提供了一个取得其对象的方法

实现单例设计模式的十种方式

  • 饿汉式 线程安全的(静态成员)

  • 饿汉式 线程安全的(静态代码块)

  • 懒汉式 线程不安全的 (一次判断)

  • 懒汉式 线程不安全的 (两次判断)

  • 懒汉式 线程安全的 (方法加锁)

  • 懒汉式 线程安全的 (静态代码块加锁)

  • 懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁

  • 懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁+Volatile关键字

  • 静态内部类 线程安全的

  • 枚举 绝对安全的


饿汉式 线程安全的(静态成员)

步骤

  • 创建一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法

代码实现

public class Case01 {

    private static Case01 instance = new Case01();

    private Case01() {
    }

    public static Case01 getInstance() {
        return instance;
    }
}

优缺点比较

  • 优点
    • 这种写法比较简单,就是在类加载的时候就去完成实例化,避免了线程同步的问题
  • 缺点
    • 在类装载的时候就完成了实例化,没有达到懒加载的效果,如果从始至终都没有使用过这个实例,那么这个实例就浪费了内存
  • 这种方式基于 classloder机制避免了多线程的同步问题.不过,instance在类加载的时候就实例化,在单例模式中大多数都是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他种方法(或者其他种静态方法)导致类装载,这个时候 就没有懒加载的效果
  • 结论:这种单例模式可用,但是 可能 造成内存浪费

饿汉式 线程安全的(静态代码块)

步骤

  • 声明一个私有对象
  • 静态代码块创建对象
  • 私有化构造器
  • 暴漏一个外界访问的方法

代码实现

public class Case02 {
    private static Case02 instance;

    static {
        instance = new Case02();
    }

    private Case02() {

    }

    public static Case02 getInstance() {
        return instance;
    }
}

优缺点比较

  • 种方式和上面的方式其实类似,只不过将类实例化放在静态代码块,但是需要注意的是,instance的声明必须在静态代码块之前,否则可能会 为 null 。
  • 其实也就是在类装载的时候,就执行静态代码块中的代码,初始化类的实例,优缺点和上面是一样的
  • 结论:这种单例模式可用,但是 可能 会造成 内存的浪费

懒汉式 线程不安全的 (一次判断)

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

public class Case03 {
    private static Case03 instance;

    private Case03() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static Case03 getInstance() {
        if (instance == null) {
            instance = new Case03();
        }
        return instance;
    }
}

优缺点

  • 优点,起到了懒加载的效果,什么时候用什么时候再加载,但是只能再单线程下使用后

  • 缺点:如果在多线程情况下,一个线程进入了if(instance == null)还没有来得及向下进行,而另外一个线程也执行了这个判断语句,这个时候会产生多个实例,所以在多线程情况下不可以使用这种方式

  • 总结:实际开发中不可以使用这种方式

懒汉式 线程不安全的 (两次判断)

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

public class Case04 {
    private static Case04 instance;

    private Case04() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static Case04 getInstance() {
        if (instance == null) {
            if (instance == null) {
                instance = new Case04();
            }
        }
        return instance;
    }
}

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,但是只能再单线程下使用后,

  • 缺点:如果在多线程情况下,一个线程进入了if(instance == null)还没有来得及向下进行,而另外一个线程也执行了这个判断语句,这个时候会产生多个实例,所以在多线程情况下不可以使用这种方式,虽然多判断了一次,但是还是会多线程并发访问的问题

  • 总结:实际开发中不可以使用这种方式

懒汉式 线程安全的 (方法加锁)

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

public class Case05 {
    private static Case05 instance;

    private Case05() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized static Case05 getInstance() {
        if (instance == null) {
            instance = new Case05();
        }
        return instance;
    }
}

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,因为方法加锁,是线程安全的

  • 缺点:锁所用于方法,范围太大了,影响效率

  • 总结:实际开发中可以使用这种方式,但是又更好的方法

懒汉式 线程安全的 (静态代码块加锁)

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

public class Case06 {
    private static Case06 instance;

    private Case06() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static Case06 getInstance() {
        synchronized (Case06.class) {
            if (instance == null) {
                instance = new Case06();
            }
            return instance;
        }
    }
}

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,因为方法加锁,是线程安全的

  • 缺点:锁所用于代码块。每次进入都要一次加锁判断,范围太大了,影响效率

  • 总结:实际开发中可以使用这种方式,但是又更好的方法

懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

public class Case07 {
    private static Case07 instance;

    private Case07() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static Case07 getInstance() {
        if (instance == null) {
            synchronized (Case07.class) {
                if (instance == null) {
                    instance = new Case07();
                }
            }
        }
        return instance;
    }
}

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,因为方法加锁,是线程安全的

  • 缺点:锁所用于代码块。第一次进入都要一次加锁判断,之后进入不需要加锁,但是有可能会出问题

  • 总结:实际开发中可以使用这种方式,但是又更好的方法

懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁+Volatile关键字

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

public class Case08 {
    private volatile static Case08 instance;

    private Case08() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static Case08 getInstance() {
        if (instance == null) {
            synchronized (Case08.class) {
                if (instance == null) {
                    instance = new Case08();
                }
            }
        }
        return instance;
    }
}

优缺点

  • 优点:起到了懒加载的效果,什么时候用什么时候再加载,因为方法加锁,是线程安全的

  • 添加了 volatile 关键字修饰,禁止了指令重排序,更安全了一些

  • 缺点:锁所用于代码块。第一次进入都要一次加锁判断,之后进入不需要加锁,但是有可能会出问题

  • 总结:实际开发中使用这种方式

静态内部类 线程安全的

步骤

  • 声明一个私有对象
  • 私有化构造器
  • 暴漏一个外界访问的方法 在方法里面进行控制判断

代码实现

public class Case09 {
    private Case09() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class Inner {
        private static Case09 instance = new Case09();
    }

    public static Case09 getInstance() {
        return Inner.instance;
    }
}

优缺点

  • 优点:是线程安全的,跟随类加载而加载

  • 缺点:可能会创建了对象不使用,浪费空间

枚举 绝对安全的

步骤

  • 枚举类只有一个实例
  • 暴漏一个外界访问的方法

代码实现

public enum Case10{
    INSTANCE;

    public static Case10 getInstance() {
        return INSTANCE;
    }
}

优缺点

  • 缺点:是饿汉式可能浪费空间
  • 优点:绝对安全

总结

  • 单例模式保证了 系统内存种只有该类的一个对象,节省了系统资源,对于一些需要频繁创建和销毁的对象,使用单例模式可以提高系统性能

  • 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new

  • 单例模式使用的场景:需要频繁的进行创建和销毁的对象,创建对象的时候消耗过多或耗费资源过多(也就是:重量级对象) 但又是经常用到的对象,工具类对象,频繁访问数据库或文件的对象(比如数据源.session工厂等)

  • 对于非枚举实现的单例设计模式,可以通过反射创建对象,但是一般不会这么做,而枚举类型通过反射创建对象的时候,会抛异常

  • 推荐使用双重检查锁+Volatile关键字

1
https://gitee.com/elonchung/Java-Review.git
git@gitee.com:elonchung/Java-Review.git
elonchung
Java-Review
Java-Review
master

搜索帮助