单例模式
本篇是读《设计模式之禅(第2版)》这本书的笔记,希望大家都读一下这本书,很有意思的一本书。
定义:Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类
只有一个实例,而且自行实例化并向整个系统提供这个实例。)
一、优缺点
1.优点
- 由于单例模式在内存中只有一个实例,减少了内存开支。
- 减少了系统的性能开销(在Java EE中采用单例模式时需要注意JVM
垃圾回收机制) - 避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在
内存中,避免对同一个资源文件的同时写操作 - 可以在系统设置全局的访问点,优化和共享资源访问
2.缺点
- 没有接口,扩展很困难
- 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行
测试的,没有接口也不能使用mock的方式虚拟一个对象 - 单例模式与单一职责原则有冲突
二、使用场景
Spring中的每个Bean就是一个单例,这样做的优点是Spring容器可以管理这些Bean的生命期
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)。
三、注意事项
- 高并发情况下线程不安全
- 对象的复制问题,最好不要实现Cloneable接口
四、上代码
1.懒汉式
1.1通过synchronized和 volatile 解决线程安全问题
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过synchronized解决,但也带来效率下降
*/
public class LazyLoadingDemo1 {
private static volatile LazyLoadingDemo1 INSTANCE;
private LazyLoadingDemo1() {
}
public static LazyLoadingDemo1 getInstance() {
if (INSTANCE == null) {
//双重检查
synchronized (LazyLoadingDemo1.class) {
if(INSTANCE == null) {
INSTANCE = new LazyLoadingDemo1();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(LazyLoadingDemo1.getInstance().hashCode());
}).start();
}
}
}
1.2.通过静态内部类方式
/**
* 静态内部类方式
* JVM保证单例
* 加载外部类时不会加载内部类,这样可以实现懒加载
*/
public class LazyLoading {
private LazyLoading() {
}
private static class LazyLoadingHolder {
private final static LazyLoading INSTANCE = new LazyLoading();
}
public static LazyLoading getInstance() {
return LazyLoadingHolder.INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(LazyLoading.getInstance().hashCode());
}).start();
}
}
}
2.饿汉式
/**
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用!
* 唯一缺点:不管用到与否,类装载时就完成实例化
* Class.forName("")
* (话说你不用的,你装载它干啥)
*/
public class Hungry {
private static final Hungry INSTANCE = new Hungry();
private Hungry() {};
public static Hungry getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
Hungry hungry1 = Hungry.getInstance();
Hungry hungry2 = Hungry.getInstance();
System.out.println(hungry1 == hungry2);
}
}
3.最优枚举类
/**
* 不仅可以解决线程同步,还可以防止反序列化。
*/
public enum SingletonDemo {
INSTANCE;
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(SingletonDemo.INSTANCE.hashCode());
}).start();
}
}
}