核心作用
保证一个类只有一个实例,并且对外提供一个访问该实例的全局访问点。
优点
- 由于单例模式只生成一个实例,减少系统性能开销;
- 单例模式可以在系统设置全局访问点,优化环境共享资源访问;
常用应用场景
需要频繁的创建和销毁的对象,创建对象耗时过多或者消耗资源过多(即重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如:数据源、Session工厂等)
- windows 系统的任务管理器、回收站;
- 项目中读取配置文件的类,一般配置文件只需要读取一次,所以只需要一个读取配置文件的类;
- 网站的计数器
- 应用程序中日志应用,一般都是单例模式实现,由于共享的日志文件一直处于打开状态,只能有一个实例操作,否则不好追加
- 数据库的连接池设计
- 操作系统的文件系统
- 在 Spring 中,每个 bean 默认是单例,这样做的优点是 Spring 容器很好的管理
- 在 Servlet 编程中,每个 Servlet 也是单例
- 在 SpringMVC 中,控制器对象也是单例
核心步骤
- 私有化构造方法
- 提供私有的静态的对象的实例属性
- 提供对外访问的静态方法
八种单例模式
- 饿汉式(静态常量):线程安全,调用效率高,但是不能延时加载;
- 饿汉式(静态代码块):线程安全,调用效率高,但是不能延时加载;
- 懒汉式(普通懒汉式):线程不安全;
- 懒汉式(同步方法):线程安全,调用效率不高,但是可以延时加载;
- 懒汉式(同步代码块):线程不安全;
- 双重检测锁式:起到懒加载的效果,线程也安全,比同步方法效率高一些。比同步方法效率高一些,双重检查保证线程安全,由于 JVM 底层内部模型原因,偶尔会出现问题,需要使用 volatile 防止指令重排;
- 静态内部类式:线程安全,调用效率高,可以延时加载;
- 枚举单例:线程安全,调用效率高,不能延时加载,实现简单。由于 JVM 从根本上提供保障,避免通过反射和反序列化的漏洞;
两种饿汉式
【不推荐】饿汉式(静态常量)
package top.simba1949.singleton.pattern;
/**
* 饿汉式——静态常量
* 基本步骤:
* 1. 构造器私有化
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
* <p>
* 优缺点
* 优点:写法简单,在类装载的时候完成实例化,避免多线程问题
* 缺点:没有进行懒加载,如果没有使用到会造成内存浪费
* <p>
* 结论: 可以使用,但不推荐
*
* @author anthony
* @date 2020/5/7 11:23
*/
public class HungrySingletonByStaticConstant {
/**
* 1. 构造器私有化
*/
private HungrySingletonByStaticConstant() {
}
/**
* 2. 在类的内部创建对象
*/
private final static HungrySingletonByStaticConstant instance = new HungrySingletonByStaticConstant();
/**
* 3. 向外暴露一个静态的公共方法
*
* @return
*/
public static HungrySingletonByStaticConstant getInstance() {
return instance;
}
}
【不推荐】饿汉式(静态代码块)
package top.simba1949.singleton.pattern;
/**
* 饿汉式——静态代码块
* 基本步骤:
* 1. 构造器私有化
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
* <p>
* 优缺点
* 优点:写法简单,在类装载的时候完成实例化,避免多线程问题
* 缺点:没有进行懒加载,如果没有使用到会造成内存浪费
* <p>
* 结论: 可以使用,但不推荐
*
* @Author anthony
* @Date 2020/5/7 11:23
*/
public class HungrySingletonByStaticCodeBlock {
/**
* 1. 构造器私有化
*/
private HungrySingletonByStaticCodeBlock() {
}
/**
* 2. 在类的内部创建对象
*/
private static HungrySingletonByStaticCodeBlock instance;
static {
// 静态代码块中,初始化
instance = new HungrySingletonByStaticCodeBlock();
}
/**
* 3. 向外暴露一个静态的公共方法
*
* @return
*/
public static HungrySingletonByStaticCodeBlock getInstance() {
return instance;
}
}
三种懒汉式
【禁用】懒汉式(普通懒汉式)
package top.simba1949.singleton.pattern.impl;
/**
* 懒汉式——线程不安全
* 基本步骤:
* 1. 构造器私有化
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
* <p>
* 优缺点:
* 优点:起到懒加载的效果,但是只能用于单线程下使用
* 缺点:多线程下,一个线程进入if (null == instance){}判断语句,还未创建对象,另一个线程也进入这个判断语句,此时会产生多个实例
* <p>
* 结论:禁用
*
* @Author anthony
* @Date 2020/5/7 11:43
*/
public class LazySingletonByCommon {
/**
* 1. 构造器私有化
*/
private LazySingletonByCommon() {
}
private static LazySingletonByCommon instance;
/**
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
*
* @return
*/
public static LazySingletonByCommon getInstance() {
if (null == instance) {
instance = new LazySingletonByCommon();
}
return instance;
}
}
【不推荐】懒汉式(同步方法)
package top.simba1949.singleton.pattern;
/**
* 懒汉式——同步方法
* 基本步骤:
* 1. 构造器私有化
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
* <p>
* 优缺点:
* 优点:起到懒加载的效果,线程也安全,但是使用同步方法,效率低下
* 缺点:使用同步方法效率低下
* <p>
* 结论:可以使用,但不推荐
*
* @Author anthony
* @Date 2020/5/7 15:19
*/
public class LazySingletonBySyncMethod {
/**
* 1. 构造器私有化
*/
private LazySingletonBySyncMethod() {
}
private static LazySingletonBySyncMethod instance;
/**
* 2. 在类的内部创建对象,使用 synchronized 进行加锁保证线程安全
* 3. 向外暴露一个静态的公共方法
*
* @return
*/
public synchronized static LazySingletonBySyncMethod getInstance() {
if (null == instance) {
instance = new LazySingletonBySyncMethod();
}
return instance;
}
}
【禁用】懒汉式(同步代码块)
package top.simba1949.singleton.pattern;
/**
* 懒汉式——同步代码块,线程不安全,禁用
* 基本步骤:
* 1. 构造器私有化
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
* <p>
* 优缺点:
* 优点:起到懒加载的效果,比同步方法效率高一些,但是线程不安全
* 缺点:比同步方法效率高一些,但是这种同步并不能起到线程安全问题
* <p>
* 结论:禁用
*
* @Author anthony
* @Date 2020/5/7 15:22
*/
public class LazySingletonBySyncCode {
/**
* 1. 构造器私有化
*/
private LazySingletonBySyncCode() {
}
private static LazySingletonBySyncCode instance;
/**
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
*
* @return
*/
public static LazySingletonBySyncCode getInstance() {
if (null == instance) {
synchronized (LazySingletonBySyncCode.class) {
// 只要有多个线程进入到 if 语句块中,每个线程会依次执行new对象操作(线程不安全)
instance = new LazySingletonBySyncCode();
}
}
return instance;
}
}
【推荐】双重检测锁式
package top.simba1949.singleton.pattern.impl;
/**
* 懒汉式——双重检查(同步代码块)
* 基本步骤:
* 1. 构造器私有化
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
* <p>
* 优缺点:
* 优点:起到懒加载的效果,线程也安全,比同步方法效率高一些
* 比同步方法效率高一些,双重检查保证线程安全,(由于 JVM 底层内部模型原因,偶尔会出现问题,需要使用 volatile 防止指令重排)
* <p>
* 结论:推荐使用
*
* @Author anthony
* @Date 2020/5/9 9:59
*/
public class SingletonByDoubleCheckSync {
/**
* 1. 构造器私有化
*/
private SingletonByDoubleCheckSync() {
}
/**
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
*
* @return
*/
private volatile static SingletonByDoubleCheckSync instance;
public static SingletonByDoubleCheckSync getInstance() {
if (null == instance) {
synchronized (SingletonByDoubleCheckSync.class) {
if (null == instance) {
instance = new SingletonByDoubleCheckSync();
}
}
}
return instance;
}
}
【推荐】静态内部类式
package top.simba1949.singleton.pattern;
/**
* 懒汉式——静态内部类
* 基本步骤:
* 1. 构造器私有化
* 2. 在类的内部创建对象
* 3. 向外暴露一个静态的公共方法
* <p>
* 优缺点:
* 优点:起到懒加载的效果,线程也安全(采用类装载机制保证初始化实例时只有一个线程),效率高
* 静态内部类方式在SingletonSeven类被装载是不会立即实例化,调用 getInstance() 方法才会装载,从而完成单例实例化
* 类的静态属性只会在第一次加载类时初始化,JVM底层保证了线程安全,在类进行初始化时,其他线程无法进入
* <p>
* 结论:推荐使用
*
* @Author anthony
* @Date 2020/5/9 10:11
*/
public class SingletonByStaticInnerClass {
/**
* 1. 构造器私有化
*/
private SingletonByStaticInnerClass() {
}
private static class Inner {
/**
* 写一个静态内部类,该类中有一个静态属性 SingletonByStaticInnerClass
* 2. 在类的内部创建对象
*/
private static final SingletonByStaticInnerClass INSTANCE = new SingletonByStaticInnerClass();
}
/**
* 3. 向外暴露一个静态的公共方法
*
* @return
*/
public static SingletonByStaticInnerClass getInstance() {
return Inner.INSTANCE;
}
}
【推荐】枚举单例
package top.simba1949.singleton.pattern.impl;
/**
* 懒汉式——枚举
* <p>
* 优缺点:
* 优点:线程安全,调用效率高,不能延时加载,实现简单。由于 JVM 从根本上提供保障,避免通过反射和反序列化的漏洞
* <p>
* 结论:推荐使用
*
* @Author anthony
* @Date 2020/5/9 10:37
*/
public enum SingletonByEnum {
INSTANCE;
}
防止破坏单例模式的三种方式
防止通过反射创建多个对象
package top.simba1949.singleton.pattern;
/**
* 避免反射创建多个对象,在构造方法中判断如果实例属性不为 null 时,抛出异常即可
*
* @author anthony
* @datetime 2020/5/9 10:51
*/
public class SingletonByReflect {
private static final SingletonByReflect instance = new SingletonByReflect();
private SingletonByReflect() {
if (null != instance) {
throw new RuntimeException();
}
}
public static SingletonByReflect getInstance() {
return instance;
}
}
防止通过反序列化创建多个对象
详见 java-io 中的 ObjectInputStream 的对象;
package top.simba1949.singleton.pattern;
import java.io.Serializable;
/**
* 添加 readResolve() 方法可以避免反序列化创建多个对象
*
* @author anthony
* @datetime 2020/5/9 10:53
*/
public class SingletonBySerializable implements Serializable {
private static final long serialVersionUID = 5155396228091341764L;
private static final SingletonBySerializable instance = new SingletonBySerializable();
private SingletonBySerializable() {
}
public static SingletonBySerializable getInstance() {
return instance;
}
/**
* 添加 readResolve() 方法可以避免反序列化创建多个对象
*
* @return
*/
private Object readResolve() {
return instance;
}
}
防止通过clone创建多个对象
package top.simba1949.singleton.pattern;
/**
* 防止克隆
*
* @author anthony
* @datetime 2020/5/9 10:51
*/
public class SingletonByClone implements Cloneable {
private static final SingletonByClone instance = new SingletonByClone();
private SingletonByClone() {
}
public static SingletonByClone getInstance() {
return instance;
}
/**
* 防止克隆破坏
*
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
// 下面这一行代码,虽然重写了 clone 方法,但是实际上调用 Object 的 clone 方法,相当于没有重写
// 用于验证 clone 确实破坏了单例
// return super.clone();
// 下面返回是防止 clone 破坏单例模式
return instance;
}
}
测试类
package top.simba1949.singleton.pattern;
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import top.simba1949.singleton.pattern.impl.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.concurrent.CountDownLatch;
/**
* @author anthony
* @datetime 2023/7/14
*/
@Slf4j
public class SingletonPatternApplication {
public static void main(String[] args) throws Exception {
// 饿汉式——静态常量
// hungrySingletonByStaticConstant();
// 饿汉式——静态代码块
// hungrySingletonByStaticCodeBlock();
// 懒汉式——普通懒汉式
// lazySingletonByCommon();
// 懒汉式——同步方法
// lazySingletonBySyncMethod();
// 懒汉式——同步代码块
// lazySingletonBySyncCode();
// 双重检测锁
// singletonByDoubleCheckSync();
// 静态内部类
// singletonByStaticInnerClass();
// 枚举单例
// singletonByEnum();
// 防止反射破坏单例
// singletonByReflect();
// 防止反序列化破坏单例
// singletonBySerializable();
// 防止 clone 破坏单例
singletonByClone();
}
/**
* 饿汉式——静态常量
*
* @throws Exception
*/
public static void hungrySingletonByStaticConstant() throws Exception {
ConcurrentHashSet<HungrySingletonByStaticConstant> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
HungrySingletonByStaticConstant instance = HungrySingletonByStaticConstant.getInstance();
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
Assert.isTrue(1 == container.size(), "容器内对象不是同一个对象");
}
/**
* 饿汉式——静态代码块
*
* @throws Exception
*/
public static void hungrySingletonByStaticCodeBlock() throws Exception {
ConcurrentHashSet<HungrySingletonByStaticCodeBlock> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
HungrySingletonByStaticCodeBlock instance = HungrySingletonByStaticCodeBlock.getInstance();
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
Assert.isTrue(1 == container.size(), "容器内对象不是同一个对象");
}
/**
* 懒汉式——普通懒汉式
* @throws Exception
*/
public static void lazySingletonByCommon() throws Exception {
ConcurrentHashSet<LazySingletonByCommon> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
LazySingletonByCommon instance = LazySingletonByCommon.getInstance();
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
Assert.isTrue(1 == container.size(), "容器内对象不是同一个对象");
}
/**
* 懒汉式——同步方法
* @throws Exception
*/
public static void lazySingletonBySyncMethod() throws Exception {
ConcurrentHashSet<LazySingletonBySyncMethod> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
LazySingletonBySyncMethod instance = LazySingletonBySyncMethod.getInstance();
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
Assert.isTrue(1 == container.size(), "容器内对象不是同一个对象");
}
/**
* 懒汉式——同步代码块
* @throws Exception
*/
public static void lazySingletonBySyncCode() throws Exception {
ConcurrentHashSet<LazySingletonBySyncCode> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
LazySingletonBySyncCode instance = LazySingletonBySyncCode.getInstance();
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
Assert.isTrue(1 == container.size(), "容器内对象不是同一个对象");
}
/**
* 双重检查
* @throws Exception
*/
public static void SingletonByDoubleCheckSync() throws Exception {
ConcurrentHashSet<SingletonByDoubleCheckSync> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
SingletonByDoubleCheckSync instance = SingletonByDoubleCheckSync.getInstance();
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
Assert.isTrue(1 == container.size(), "容器内对象不是同一个对象");
}
/**
* 静态内部类
* @throws Exception
*/
public static void singletonByStaticInnerClass() throws Exception {
ConcurrentHashSet<SingletonByStaticInnerClass> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
SingletonByStaticInnerClass instance = SingletonByStaticInnerClass.getInstance();
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
Assert.isTrue(1 == container.size(), "容器内对象不是同一个对象");
}
/**
* 枚举单例
* @throws Exception
*/
public static void singletonByEnum() throws Exception {
ConcurrentHashSet<SingletonByEnum> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
SingletonByEnum instance = SingletonByEnum.INSTANCE;
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
Assert.isTrue(1 == container.size(), "容器内对象不是同一个对象");
}
/**
* 通过反射获取实例
* @throws Exception
*/
public static void singletonByReflect() throws Exception {
ConcurrentHashSet<SingletonByReflect> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
try {
// 通过反射获取实例
SingletonByReflect instance = SingletonByReflect.class.newInstance();
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
} catch (Exception e) {
log.info("获取到异常,异常信息为{}", e.getMessage(), e);
}
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
// 这里因为代码中是饿汉式(静态常量)模式,实例已经存在了,在通过反序列化后会一直报异常
Assert.isTrue(1 >= container.size(), "容器内对象不是同一个对象");
}
/**
* 通过反射获取实例
* @throws Exception
*/
public static void singletonBySerializable() throws Exception {
String path = "d:\\SingletonBySerializable.txt";
// 获取对象
SingletonBySerializable instance4Serializable = SingletonBySerializable.getInstance();
// 先将对象进行序列化
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(new File(path).toPath()));
oos.writeObject(instance4Serializable);
ConcurrentHashSet<SingletonBySerializable> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
try {
// 通过反序列化获取对象
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(path)));
SingletonBySerializable instance = (SingletonBySerializable)ois.readObject();
log.info("获取实例的hashCode值为:{}", instance.hashCode());
container.add(instance);
} catch (Exception e) {
log.info("获取到异常,异常信息为{}", e.getMessage(), e);
}
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
// 这里因为代码中是饿汉式(静态常量)模式,实例已经存在了,在通过反序列化后会一直报异常
Assert.isTrue(1 >= container.size(), "容器内对象不是同一个对象");
}
/**
* 通过反射获取实例
* @throws Exception
*/
public static void singletonByClone() throws Exception {
// 获取对象
SingletonByClone instance4Original = SingletonByClone.getInstance();
ConcurrentHashSet<SingletonByClone> container = new ConcurrentHashSet<>();
CountDownLatch countDownLatch = new CountDownLatch(1000);
// 连续获取对象,如果相等说明是同一个对象
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
try {
// 通过反序列化获取对象
SingletonByClone clone = (SingletonByClone) instance4Original.clone();
log.info("获取实例的hashCode值为:{}", clone.hashCode());
container.add(clone);
} catch (Exception e) {
log.info("获取到异常,异常信息为{}", e.getMessage(), e);
}
countDownLatch.countDown();
}
}).start();
}
// 等待所有线程执行完成
countDownLatch.await();
// 这里因为代码中是饿汉式(静态常量)模式,实例已经存在了,在通过反序列化后会一直报异常
Assert.isTrue(1 >= container.size(), "容器内对象不是同一个对象");
}
}
单例模式在 JDK 的应用
Runtime是java application与系统的接口, 可以启动单独的进程执行外部命令;监控方法甚至指令的执行;加载动态库(依赖于ClassLoader);可以报告、控制java app的状态(内存使用、暂停、退出等);
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}