目录
1 单例模式介绍
1 ) 定义
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。
2 ) 使用单例模式要做的两件事
- 保证一个类只有一个实例
- 为该实例提供一个全局访问节点
3 ) 单例模式结构
1.2 饿汉式
在类加载期间初始化静态实例,保证 instance 实例的创建是线程安全的 ( 实例在类加载时实例化,有JVM保证线程安全).
特点: 不支持延迟加载实例(懒加载) , 此中方式类加载比较慢,但是获取实例对象比较快
问题: 该对象足够大的话,而一直没有使用就会造成内存的浪费。
public class Singleton_01 {
//1. 私有构造方法
private Singleton_01(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_01 instance = new Singleton_01();
//3. 提供一个全局访问点,供外部获取单例对象
public static Singleton_01 getInstance(){
return instance;
}
}
1.3 懒汉式(线程不安全)
此种方式的单例实现了懒加载,只有调用getInstance方法时 才创建对象.但是如果是多线程情况,会出现线程安全问题.
public class Singleton_02 {
//1. 私有构造方法
private Singleton_02(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_02 instance;
//3. 通过判断对象是否被初始化,来选择是否创建对象
public static Singleton_02 getInstance(){
if(instance == null){
instance = new Singleton_02();
}
return instance;
}
}
1.4 懒汉式(线程安全)
原理: 使用同步锁 synchronized
锁住 创建单例的方法 ,防止多个线程同时调用,从而避免造成单例被多次创建
public class Singleton_03 {
//1. 私有构造方法
private Singleton_03(){
}
//2. 在本类中创建私有静态的全局对象
private static Singleton_03 instance;
//3. 通过添加synchronize,保证多线程模式下的单例对象的唯一性
public static synchronized Singleton_03 getInstance(){
if(instance == null){
instance = new Singleton_03();
}
return instance;
}
}
1.5 双重校验
饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式。
/**
* 单例模式-双重校验
* @author spikeCong
* @date 2022/9/5
**/
public class Singleton_04 {
//使用 volatile保证变量的可见性
private volatile static Singleton_04 instance = null;
private Singleton_04(){
}
//对外提供静态方法获取对象
public static Singleton_04 getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null){
synchronized (Singleton_04.class){
//抢到锁之后再次进行判断是否为null
if(instance == null){
instance = new Singleton_04();
}
}
}
return instance;
}
}
双重校验为了避免:
1.6 静态内部类
根据 静态内部类 的特性(外部类的加载不影响内部类),同时解决了按需加载、线程安全的问题,同时实现简洁
public class Singleton_05 {
private static class SingletonHandler{
private static Singleton_05 instance = new Singleton_05();
}
private Singleton_05(){}
public static Singleton_05 getInstance(){
return SingletonHandler.instance;
}
}
1.7 反射对于单例的破坏
public class Test_Reflect {
public static void main(String[] args) {
try {
//反射中,欲获取一个类或者调用某个类的方法,首先要获取到该类的Class 对象。
Class<Singleton_05> clazz = Singleton_05.class;
//getDeclaredXxx: 不受权限控制的获取类的成员.
Constructor c = clazz.getDeclaredConstructor(null);
//设置为true,就可以对类中的私有成员进行操作了
c.setAccessible(true);
Object instance1 = c.newInstance();
Object instance2 = c.newInstance();
System.out.println(instance1 == instance2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
解决方法之一: 在单例类的构造方法中 添加判断 instance != null
时,直接抛出异常
public class Singleton_05 {
private static class SingletonHandler{
private static Singleton_05 instance = new Singleton_05();
}
private Singleton_05(){
if(SingletonHandler.instance != null){
throw new RuntimeException("不允许非法访问!");
}
}
public static Singleton_05 getInstance(){
return SingletonHandler.instance;
}
}
1.8 序列化对于单例的破坏
/**
* 序列化对单例的破坏
* @author spikeCong
* @date 2022/9/6
**/
public class Test_Serializable {
@Test
public void test() throws Exception{
//序列化对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));
oos.writeObject(Singleton.getInstance());
//序列化对象输入流
File file = new File("tempFile.obj");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton Singleton = (Singleton) ois.readObject();
System.out.println(Singleton);
System.out.println(Singleton.getInstance());
//判断是否是同一个对象
System.out.println(Singleton.getInstance() == Singleton);//false
}
}
/**
* 单例类实现序列化接口
*/
class Singleton implements Serializable {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
解决方案
/**
* 解决方案:只要在Singleton类中定义readResolve就可以解决该问题
* 程序会判断是否有readResolve方法,如果存在就在执行该方法,如果不存在--就创建一个对象
*/
private Object readResolve() {
return singleton;
}
1.9 枚举(推荐方式)
满足单例模式所需的 创建单例、线程安全、实现简洁的需求
public enum Singleton_06{
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Singleton_06 getInstance(){
return INSTANCE;
}
}
为什么枚举类可以阻止反射的破坏?
: 反射方法中不予许使用反射创建枚举对象
为什么枚举类可以阻止序列化的破坏?
Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。
1.10 单例模式总结
1 ) 单例的定义 单例设计模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。
2 ) 单例的实现
饿汉式
- 饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
懒汉式
- 相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
双重检测
- 双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。所以,这种实现方式解决了懒汉式并发度低的问题。
静态内部类
- 利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。
枚举方式
- 最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性(同时阻止了反射和序列化对单例的破坏)。