创建型模式 - 单例设计模式
简介
- 所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类 只能存在一个对象实例,并且该类提供了一个取得其对象的方法
实现单例设计模式的十种方式
饿汉式 线程安全的(静态成员)
饿汉式 线程安全的(静态代码块)
懒汉式 线程不安全的 (一次判断)
懒汉式 线程不安全的 (两次判断)
懒汉式 线程安全的 (方法加锁)
懒汉式 线程安全的 (静态代码块加锁)
懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁
懒汉式 线程安全的 (静态代码块加锁 - 缩小静态代码块的区域)线程安全的双重检查锁+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关键字