从设计层面上讲,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
比如在Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量的,一般情况下,一个项目通常只需要一个SessionFactory就够,这时就会使用到单例模式。
-
某一个类只有一个实例;(构造器私有)
-
它必须自行创建这个实例;(自己编写实例化逻辑)
-
它必须自己向整个系统提供这实例;(对外提供实例化方法)
单例模式种类
静态常量饿汉式
1.代码实现
/**
* 饿汉式(静态变量)
*
* @author caoweifeng
* @date 2022年02月24日 5:05 PM
* @email weistuday@qq.com
* @description:
*/
public class Type1 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1); // true
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
class Singleton {
/**
* 1.构造器私有化,外部不能new
*/
private Singleton() {
}
/**
* 2. 本类内部创建对象实例
*/
private final static Singleton INSTANCE = new Singleton();
/**
* 3. 提供一个公有的静态方法,返回实例对象
*/
public static Singleton getInstance() {
return INSTANCE;
}
}
2.优缺点说明
-
优点 : 这种写法比较简单,就是在类装载点时候完成实例化,避免例线程同步问题。
-
缺点: 在类装载点时候就完成实例化,没有达成懒加载点效果,如果从始至终从未使用过这个实例,则会造成内存的浪费
-
这种方式基于classloder(类加载机制)避免例多线程的同步问题,不过,instance在类装载的时候就实例化,在单例模式中大多数都是调用getinstance方法,不过导致类装载的原因有很多,因此不能确定有其他的方式(或者静态方法)导致类装载,这时候初始化instance就没有达到懒加载的效果
静态代码块饿汉式
1. 代码实现
/**
* 静态变量(饿汉式)
*
* @author caoweifeng
* @date 2022年02月24日 5:24 PM
* @email weistuday@qq.com
* @description:
*/
public class Type2 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1); // true
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
class Singleton {
/**
* 1.构造器私有化,外部不能new
*/
private Singleton() {
}
/**
* 2. 本类内部创建对象实例
*/
private static final Singleton INSTANCE;
/*
静态代码块中,创建单例对象
*/
static {
INSTANCE = new Singleton();
}
/**
* 3. 提供一个公有的静态方法,返回实例对象
*/
public static Singleton getInstance() {
return INSTANCE;
}
}
2. 优缺点说明
- 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面的是一样的。
- 结论: 这种单例模式可用,但是可能造成内存浪费
线程不安全懒汉式
1. 代码实现
/**
* 懒汉式(线程不安全)
*
* @author caoweifeng
* @date 2022年02月24日 5:24 PM
* @email weistuday@qq.com
* @description:
*/
public class Type3 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1); // true
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
class Singleton {
/**
* 1.构造器私有化,外部不能new
*/
private Singleton() {
}
/**
* 2. 静态变量
*/
private static Singleton INSTANCE;
/**
* 3. 提供一个公有的静态方法,当使用该方法时,才去创建instance
*/
public static Singleton getInstance() {
if (null == INSTANCE) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
2. 优缺点
- 起到了懒加载的效果,但只能在单线程下使用
- 如果在多线程下,一个线程进入了if(null = singleton)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时就会产生多个实例。所以在多线程环境下不可使用这种方式。
- 结论: 在实际开发种,不要使用这种方式。
线程安全懒汉式
1. 代码实现
package cn.weifeng.creatation.singleton.type4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 懒汉式(线程安全)
*
* @author caoweifeng
* @date 2022年02月24日 5:24 PM
* @email weistuday@qq.com
* @description:
*/
public class Type4 {
public static void main(String[] args) {
//测试
// Singleton instance = Singleton.getInstance();
// Singleton instance1 = Singleton.getInstance();
// System.out.println(instance == instance1); // true
// System.out.println(instance.hashCode());
// System.out.println(instance1.hashCode());
//多线程情况下测试
ExecutorService pool = Executors.newWorkStealingPool(150);
for (int i = 0; i < 150; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName());
Singleton instance = Singleton.getInstance();
System.out.println(instance.hashCode());
});
}
}
}
class Singleton {
/**
* 1.构造器私有化,外部不能new
*/
private Singleton() {}
/**
* 2. 静态变量
*/
private static Singleton INSTANCE;
/**
* 3. 提供一个公有的静态方法,当使用该方法时,才去创建instance
* 加入同步代码块
*/
public static synchronized Singleton getInstance() {
if (null == INSTANCE) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
2. 优缺点
-
解决了线程不安全的问题
-
效率太低了,每一个线程要获取实例对象的时候都需要执行同步代码块操作,其实这个只要执行一次获取实例的办法,后面直接返回就行
-
实际开发不推荐使用
同步代码块懒汉式
1. 代码实现
package cn.weifeng.creatation.singleton.type5;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 懒汉式(线程安全)
*
* @author caoweifeng
* @date 2022年02月24日 5:24 PM
* @email weistuday@qq.com
* @description:
*/
public class Type5 {
public static void main(String[] args) {
//测试
// Singleton instance = Singleton.getInstance();
// Singleton instance1 = Singleton.getInstance();
// System.out.println(instance == instance1); // true
// System.out.println(instance.hashCode());
// System.out.println(instance1.hashCode());
//多线程情况下测试
ExecutorService pool = Executors.newWorkStealingPool(150);
for (int i = 0; i < 150; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName());
Singleton instance = Singleton.getInstance();
System.out.println(instance.hashCode());
});
}
}
}
class Singleton {
/**
* 1.构造器私有化,外部不能new
*/
private Singleton() {}
/**
* 2. 静态变量
*/
private static Singleton INSTANCE;
/**
* 3. 提供一个公有的静态方法,当使用该方法时,才去创建instance
* 加入同步代码块
*/
public static Singleton getInstance() {
if (null == INSTANCE) {
synchronized (Singleton.class){
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
2 .优缺点
-
本意是想改进前面的那种同步代码效率低的问题
-
但是这种方式并不能改变线程同步不安全的问题
-
实体开发不推荐这种实现方式
DoubleCheck
1. 代码实现
package cn.weifeng.creatation.singleton.type6;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 双重检查
*
* @author caoweifeng
* @date 2022年02月24日 5:24 PM
* @email weistuday@qq.com
* @description:
*/
public class Type6 {
public static void main(String[] args) {
//测试
// Singleton instance = Singleton.getInstance();
// Singleton instance1 = Singleton.getInstance();
// System.out.println(instance == instance1); // true
// System.out.println(instance.hashCode());
// System.out.println(instance1.hashCode());
//多线程情况下测试
ExecutorService pool = Executors.newWorkStealingPool(150);
for (int i = 0; i < 150; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName());
Singleton instance = Singleton.getInstance();
System.out.println(instance.hashCode());
});
}
}
}
class Singleton {
/**
* 1.构造器私有化,外部不能new
*/
private Singleton() {}
/**
* 2. 静态变量
*/
private static volatile Singleton INSTANCE;
/**
* 3. 提供一个公有的静态方法,当使用该方法时,才去创建instance
* 加入双重检查机制,解决线程同步问题,同时解决懒加载
*/
public static Singleton getInstance() {
if (null == INSTANCE) {
synchronized (Singleton.class){
if (null == INSTANCE) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
2. 优缺点
-
双重检查概念是多线程开发中常使用的,如代码所示,我们进行了两次 if(singleton ==null)检查,这样就可以保证线程安全了
-
实例化代码只用执行一次,后面再次访问的时,判断if的时候直接return实例化对象,也避免反复进行方法同步
-
线程安全;延迟加载;效率较高
-
结论: 在实际开发中,推荐使用这种单例设计模式
静态内部类
1. 代码实现
package cn.weifeng.creatation.singleton.type7;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 静态内部类
*
* @author caoweifeng
* @date 2022年02月24日 5:24 PM
* @email weistuday@qq.com
* @description:
*/
public class Type7 {
public static void main(String[] args) {
//测试
// Singleton instance = Singleton.getInstance();
// Singleton instance1 = Singleton.getInstance();
// System.out.println(instance == instance1); // true
// System.out.println(instance.hashCode());
// System.out.println(instance1.hashCode());
//多线程情况下测试
ExecutorService pool = Executors.newWorkStealingPool(150);
for (int i = 0; i < 150; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName());
Singleton instance = Singleton.getInstance();
System.out.println(instance.hashCode());
});
}
}
}
class Singleton {
/**
* 1.构造器私有化,外部不能new
*/
private Singleton() {
}
/**
* 2. 添加静态内部类
*/
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
/**
* 3. 提供一个公有的静态方法
*/
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
2. 优缺点
-
这种方式采用了类装载机制来保证初始化实例时只有一个线程
-
静态内部类方式在Singleto类被装载的时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingLetonInstance类,从而完成SingLeton的实例化
-
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证类线程的安全性,在类初始化时,别的线程时无法进入的
-
优点: 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
-
推荐使用
枚举方式
1. 代码方式
package cn.weifeng.creatation.singleton.type8;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 枚举
*
* @author caoweifeng
* @date 2022年02月24日 5:24 PM
* @email weistuday@qq.com
* @description:
*/
public class Type8 {
public static void main(String[] args) {
//测试
// Singleton instance = Singleton.getInstance();
// Singleton instance1 = Singleton.getInstance();
// System.out.println(instance == instance1); // true
// System.out.println(instance.hashCode());
// System.out.println(instance1.hashCode());
//多线程情况下测试
ExecutorService pool = Executors.newWorkStealingPool(150);
for (int i = 0; i < 150; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName());
Singleton instance = Singleton.INSTANCE;
System.out.println(instance.hashCode());
});
}
}
}
enum Singleton {
INSTANCE;
public void sayOK() {
System.out.println("OK!");
}
}
2. 优缺点
-
借助在JKD1.5中添加点枚举来实现单例对象,不仅能避免多线程同步问题,还能防止反序列化重新创建对象
-
这种方式对Effective Java作者Josh Bloch提倡对方式
-
结论: 推荐使用
应用场景
什么场景用到?
-
多线程中的连接池
-
数据库中的连接池
-
系统环境信息
-
上下午(ServletContext)
面试问题
-
系统环境信息
-
Spring怎么保证组件单例的
-
ServletContext是什么?是单例的吗?怎么保证?
-
ApplicationContext是什么?是单例的吗?怎么保证?
-
- ApplicationContext: tomcat: 一个应用(部署的一个war包)会有一个应用上下文
- ApplicationContext: Spring: 表示整个IOC容器(怎么保证单例),IOC容器中有很多组件(怎么保证单例)
-
数据库连接池是怎么创建出来的,怎么保证单例?