单例模式

参考文章

1、看懂UML类图和时序图

在这里插入图片描述

  • 车的类图结构为<< abstract >>,表示车是一个抽象类;
  • 它有两个继承类:小汽车和自行车;它们之间的关系为实现关系,使用带空心箭头的虚线表示;
  • 小汽车为与SUV之间也是继承关系,它们之间的关系为泛化关系(继承非抽象类),使用带空心箭头的实线表示;
  • 小汽车与发动机之间是组合关系,使用带实心箭头的实线表示;
  • 学生与班级之间是聚合关系,使用带空心箭头的实线表示;
  • 学生与身份证之间为关联关系,使用一根实线表示;
  • 学生上学需要用到自行车,与自行车是一种依赖关系,使用带箭头的虚线表示;

2、单例模式

2.1 定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2.2 单例模式几个好处

  1. 省略创建对象所花费的时间,减少系统开销。
  2. 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

2.3 为什么不使用全局变量确保一个类只有一个实例呢?

只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。但是,如果说这个对象非常消耗资源,而且程序某次的执行中一直没用,这样就造成了资源的浪费。利用单例模式的话,我们就可以实现在需要使用时才创建对象,这样就避免了不必要的资源浪费。

  1. 消耗资源,资源浪费。
  2. 增加程序调试和维护的困难。

2.4 单例的模式的实现

  • 饿汉方式:指全局的单例实例在类装载时构建
  • 懒汉方式:指全局的单例实例在第一次被使用时构建。

2.4.1 饿汉式(线程安全)

JVM 在加载这个类是就立马创建此类唯一的实例。

public class Singleton{
	//在静态初始化器中创建单例实例,保证线程安全
	private static Singleton uniqueInstance = new Singleton();
	
	//Singleton 类只有一个构造方法,并且是被 private 修饰的, 所以无法通过 new 构建对象实例。
	private Singleton(){}
	public static Singleton getInstance(){
		return uniqueInstance;
	}
}

2.4.2 懒汉式

2.4.2.1 非线程安全式
public class Singleton(){
	private static Singleton uniqueSingleton;
	
	private Singleton(){}

	public static getInsatnce(){
		if(uniqueSingleton == null){
			uniqueSingleton = new Singleton();
		}
		return uniqueSingleton;
	}
}

在单例实例第一次使用时构建,而不是在 JVM 在加载类时就创建。但是上面线程不安全,多个线程同时访问 getInstance() 方法时会出现问题。

2.4.2.2 线程安全方式
2.4.2.2.1 加锁
public class Singleton(){
	private static Singleton uniqueSingleton;
	private Singleton(){}
	public synchronized Singelton getInstance(){
		if(uniqueSingleton == null){
			uniqueSingleton = new Singleton();
		}	
		return uniqueSingleton;
	}
}

缺点:耗性能,会阻塞。

2.4.2.2.2 双重检查加锁

volatile : 保证不将该关键词修饰的变量缓存在线程的内存中,而是直接访问主内存(共享内存)中的变量

public class Singleton(){
	//volatile保证当 single 变量被初始化成 Singleton 实例时,多个线程可以正确处理 singleton 变量
	private volatile static Singleton singleton;
	private Singleton(){}

	public static Singleton getInstance(){
		//检查实例,如果不存在就进入同步代码块
		if(singleton == null){
			//只有第一次才执行下面语句
			synchronized(Singleton.class){
				if(singleton == null){
					//进入同步代码块后,再检查一次,如果仍是null,才创建实例
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
}

2.4.3 登记式/静态内部类

注意:被 final 修饰的静态字段不会主动初始化,在调用该字段时才会初始化。

public class Singleton(){
	private static class SingletonHolder(){
		private static final Singleton INSTANCE = new Singleton();
	}
	private Singleton (){}
	public static final Singleton getInsatance(){
		return SingletonHolder.INSTANCE;
	}
}

2.4.4 枚举方式

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。 它更简洁,自动支持序列化机制,绝对防止多次实例化 (如果单例类实现了Serializable接口,默认情况下每次反序列化总会创建一个新的实例对象,关于单例与序列化的问题可以查看这一篇文章《单例与序列化的那些事儿》),同时这种方式也是《Effective Java 》以及《Java与模式》的作者推荐的方式。

public enum Singleton{
	//定义一个枚举元素,就是单例
	INSTANCE;
	public void doSomething(){
		System.out.println("枚举方式实现单例");
	} 
}

使用方式:

public class ESTest {

	public static void main(String[] args) {
		Singleton singleton = Singleton.INSTANCE;
		singleton.doSomeThing();//output:枚举方法实现单例

	}

}

2.4.5 单例不一定实现实例的唯一性(除了枚举方式)

  1. 反射可以破坏单例模式
  2. 序列化会破坏单例
  • 原因:序列化会通过反射调用无参数的构造方法创建一个新的对象。
  • 解决方法:在双重加锁实现的单例中,在 Singleton 中定义 readResolve 方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值