单例模式放到设计模式来说是再好不过的了
阅读指导
- 本文会从需求、设计、优缺点来阐述
- 理解思想,重于编码!
单例模式
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(枚举)
本人没整明白,有兴趣的同学可以了解一下。