单例模式(史上最通俗易懂)

单例模式放到设计模式来说是再好不过的了

阅读指导

  • 本文会从需求、设计、优缺点来阐述
  • 理解思想,重于编码!

单例模式


1.简介

   单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,通俗来说是一个类的单个实例。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

   这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。


总的来说

  • 单例类只能有一个实例(static)
  • 单例类只能自己创建自己的唯一实例(private修饰构造方法,参考enum类)
  • 单例类必须给所有其他对象提供这一实例(通过自定义public方法获取实例)

2.从需求目的粗浅理解单例
  • 意图: 保证一个类仅有一个实例,并提供它的全局访问点。
  • 目的: 避免使用频繁的类频繁的创建和销毁 (spring默认创建bean是单例)
  • 缺点: 没有接口,没有继承。

注:有兴趣的同学可以去了解下spring创建单例并且可继承的bean


3.初步实现单例(饿汉)


单例类本身创建步骤

  • 私有的 静态的 创建一个实例(静态性保证唯一实例)
  • 私有的构造函数(该类不被实例化,只能类本身实例化)
  • 公有的静态方法来获取该对象

参考代码如下

/**
* 创建单例对象
**/
public class SingleonBean {
    
    

	// 创建 SingleObject 的一个对象
	private static SingleonBean instance = new SingleonBean();

	// 让构造函数为 private,这样该类就不会被实例化
	private SingleonBean() {
    
    
	}

	// 获取唯一可用的对象
	public static SingleonBean getInstance() {
    
    
		return instance;
	}

	public void doSomething() {
    
    
		System.out.println("Do Something");
	}
}

调用者

  • 只能通过(类名.公共方法)来获取该类实例。

注:相当于我们写了一个工具类,我们把这个类的方法设置为静态的,那么我们可以用类名.方法名调用这个方法

参考代码如下

/**
* 获取使用单例对象
**/
public class GetSingleonBean {
    
    

	public static void main(String[] args) {
    
    
				
		// 编译器报错 "The constructor SingleonBean() is not visible"
		// 不合法的构造函数,因为这个构造函数是private修饰不可见的
		// SingleonBean singleonBean = new SingleonBean(); <------这是被注释的代码
		
		// 正确获取唯一单例方式
		SingleonBean instance = SingleonBean.getInstance();
		
		// 用单例做一些事情
		instance.doSomething();
	}
}

4.单例进阶实现(面试热点)


实现1(懒汉式)
  • 线程不安全
  • 延迟加载(用到时再加载,避免浪费资源)
  • 严格上算不上单例(多线程不安全)

是我们基础代码实现上实现了懒加载。

代码实现

/**
 * 
 * @author fudy 构造懒汉不安全单例模式
 */
public class SingleonBean {
    
    

	// 创建 SingleObject 的一个对象延迟初始化
	private static SingleonBean instance;

	// 让构造函数为 private,这样该类就不会被实例化
	private SingleonBean() {
    
    
	}

	// 获取唯一可用的对象
	public static SingleonBean getInstance() {
    
    
		
		// 此处会有线程问题,如果线程A和线程B都执行到了步骤1,并未完成步骤2,那么就会创建多个实例
		if (instance == null) {
    
       // 步骤1
			 instance = new SingleonBean(); // 步骤2
		}
		return instance;
	}

	public void doSomething() {
    
    
		System.out.println("Do Something");
	}
}

实现2(安全懒汉式)
  • 线程安全
  • 懒加载

由于我们实现1线程不安全,我们想到最好的办法是加 synchronized 来保证安全性,同时加锁会影响效率,每次访问该方法都会进入同步代码块。

代码实现

/**
 * 
 * @author fudy 构造懒汉不安全单例模式
 */
public class SingleonBean {
    
    

	// 创建 SingleObject 的一个对象延迟初始化
	private static SingleonBean instance;

	// 让构造函数为 private,这样该类就不会被实例化
	private SingleonBean() {
    
    }

	// 加重量级锁获取唯一可用的对象(每次访问该方法都会进入同步代码块)
	public static synchronized  SingleonBean getInstance() {
    
    	
		if (instance == null) {
    
       
			 instance = new SingleonBean();
		}
		return instance;
	}

	public void doSomething() {
    
    
		System.out.println("Do Something");
	}
}

实现3(饿汉式,用的较多)

我们基础实现就是用饿汉,可以参看本文第三步的初步实现。


实现4(双检查锁)
  • 懒加载
  • 安全
  • jdk5起,(5版本对volatile进行优化)

我们用实现1的时候,由于多线程下 getInstance 可能创建多个单例,所以我们在实现2把 getInstance 加锁,此时可以保证安全性,如果这个类访问次数较多会导致性能下降问题。我们实现双检查锁来缩小同步代码块的范围,用volatile来确保执行顺序不被打乱(指令重排序)

代码实现

/**
 * 
 * @author fudy 构造懒汉不安全单例模式
 */
public class SingleonBean {
    
    

	// 使用volatile修饰可以保证禁止指令重排序
	private volatile static SingleonBean instance;

	// 让构造函数为 private,这样该类就不会被实例化
	private SingleonBean() {
    
    }

	// 获取唯一可用的对象
	public static   SingleonBean getInstance() {
    
    
		// 如果单例我们就开始创建单例(步骤1)
		if (instance == null) {
    
    
			// 此时锁定类,我们缩小同步代码块的范围(步骤2)
			synchronized(SingleonBean.class){
    
    
				// 在锁定类的同时,有可能有其他的线程已经执行到步骤3,
				// 但未执行完,我们需要再判断是否其他线程已经执行完步骤3
				if (instance == null) {
    
     
					 instance = new SingleonBean(); // 实例化对象(步骤3)
				}
			}
		}
		return instance;
	}

	public void doSomething() {
    
    
		System.out.println("Do Something");
	}
}

实现5(静态内部类)
  • 初始化加载
  • 线程安全

通过实现3我们发现它不是懒加载,是饿汉式加载。我们为了实现懒加载还有另外一种思路。调用静态内部类,用 classloader 机制来保证初始化 instance 时只有一个线程。

代码实现

/**
 * 静态内部类实现单例
 */
public class SingleBean {
    
    
	
	// 设置静态内部类,在使用时再加载其内容
	private static class PrivateSignleBean{
    
    
		// 使用final修饰后,对象引用不能改变
		private final static SingleBean INSTANCE = new SingleBean();
	}
	// 私有构造方法不能实例化对象
	private  SingleBean(){
    
    
		
	}
	// 获取单例对象
	public static final SingleBean getInstance(){
    
    
		return PrivateSignleBean.INSTANCE;
	}

}

实现6(枚举)

本人没整明白,有兴趣的同学可以了解一下。


猜你喜欢

转载自blog.csdn.net/qq_44112474/article/details/108229512