文章目录
前言
设计模式一直是我的弱项,最近也在花时间学习,趁热打铁,做一下单例模式的总结!
(文中参考的是尚学堂Java视频)
正文
单例单例,字面意思很容易理解,就是单个实例,单例模式的核心作用就是:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点(公共方法)。
单例模式的常见应用场景:
- Windows的任务管理器
- Windows的回收站
- 网站的计数器
- 应用程序的日志应用
- 数据库连接池的设计
- Application也是单例应用(Servlet会涉及到)
- 在Spring中,每个Bean默认是单例
- 在Servlet编程中,每个Servlet也是单例
- 在Spring MVC框架/struts1框架中,控制器对象也是单例
优点:
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 单例模式可以在系统设置全局的访问点、优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
单例模式的常见应用场景:
主要:
饿汉式(线程安全、调用效率高。但是不能延时加载)
懒汉式(线程安全,调用效率不高。但是可以延时加载)
其他:
双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题,不建议使用)
静态内部类式(线程安全,调用效率高。可以延时加载)
枚举单例(线程安全、调用效率高,不能延时加载)
饿汉式实现(单例对象立即加载)
要点
饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字
存在的问题
如果该类一直没有被调用,但是又自动初始化了,就会造成资源浪费
代码实现
package designpatterns.singleton;
/**
* 饿汉式单例模式
* @author Hertter
* @since 2019年8月3日下午12:17:23
*/
public class HungryTypeTest {
public static void main(String[] args) {
// 测试单例模式
HungryType hungryType1 = HungryType.getInstance();
HungryType hungryType2 = HungryType.getInstance();
// 两者的地址相等,对外存在一个对象
System.out.println(hungryType1 == hungryType2);
}
}
class HungryType {
/*
* 在类加载器加载的时候,instance会自动被实例化
*/
private static HungryType instance = new HungryType();
/*
* 私有化构造器,避免外部调用
*/
private HungryType() {}
/*
* 提供对外的获取实例的方法
* 这里不需要加synchronized,因为创建对象时,此类是在类加载器中加载的,具备天然的线程安全
* 而且不加synchronized可以提高效率
*/
public static /*synchronized*/ HungryType getInstance() {
return instance;
}
}
懒汉式实现(单例对象延迟加载)
要点:
lazy load!延迟加载,懒加载!真正用的时候才加载!
存在的问题
资源利用率高了。但是每次调用getInstance()方法都要同步,并发效率较低
代码实现
package designpatterns.singleton;
/**
* 懒汉式单例模式
* @author Hertter
* @since 2019年8月3日下午4:35:29
*/
public class LazyTypeTest {
public static void main(String[] args) {
// 测试单例模式
LazyType lazyType1 = LazyType.getInstance();
LazyType lazyType2 = LazyType.getInstance();
// 两者的地址相等,对外存在一个对象
System.out.println(lazyType1 == lazyType2);
}
}
class LazyType {
/*
* 在类加载器加载的时候,instance暂时先不初始化
*/
private static LazyType instance;
/*
* 私有化构造器,避免外部调用
*/
private LazyType() {}
/*
* 需要用到实例的时候,再进行初始化(实现了延迟加载)
* 考虑到线程安全的问题,需要加上synchronized,但会导致效率低
*/
public static synchronized LazyType getInstance() {
if (instance == null)
instance = new LazyType();
return instance;
}
}
双重检测锁实现
要点:
这个模式将同步内容下放到if内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步,创建了以后就没必要了
存在的问题
由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题(指令重排),不建议使用。
代码实现
package designpatterns.singleton;
/**
* 双重检测锁单例模式
* @author Hertter
* @since 2019年8月3日下午5:00:05
*/
public class DoubleCheckTest {
public static void main(String[] args) {
// 多线程下测试单例模式
new Thread(() -> {
System.out.println(DoubleCheck.getInstance());
}).start();
// 打印发现两者的地址相同
System.out.println(DoubleCheck.getInstance());
}
}
class DoubleCheck {
/*
* 在类加载器加载的时候,instance暂时先不初始化,类似懒汉式
*/
private static DoubleCheck instance;
/*
* 私有化构造器,避免外部调用
*/
private DoubleCheck() {}
/*
* 将同步内容下放到if内部,提高了执行效率,不必每次获取对象时都进行同步,只有第一次才需要同步
* 但是存在问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出现问题,不建议使用
*/
public static DoubleCheck getInstance() {
if (instance == null) {
synchronized(DoubleCheck.class) {
if (instance == null) {
instance = new DoubleCheck();
}
}
}
return instance;
}
}
静态内部类实现方式(也是一种懒加载方式)
要点:
- 外部类没有static属性,则不会像饿汉式那样立即加载对象。
- 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。
- instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
- 兼备了并发高效调用和延迟加载的优势!
代码实现
package designpatterns.singleton;
/**
* 静态内部类单例模式
* @author Hertter
* @since 2019年8月3日下午5:53:44
*/
public class StaticInnerClassTest {
public static void main(String[] args) {
// 测试单例模式
StaticInnerClass staticInnerClass1 = StaticInnerClass.getInstance();
StaticInnerClass staticInnerClass2 = StaticInnerClass.getInstance();
// 两者的地址相等,对外存在一个对象
System.out.println(staticInnerClass1 == staticInnerClass2);
}
}
class StaticInnerClass {
/*
* 静态内部类,在类中完成类的实例化,实现了懒加载方式
*/
private static class StaticInnerClassInstance {
private static final StaticInnerClass instance = new StaticInnerClass();
}
/*
* 方法没有synchronized,执行效率高
*/
public static StaticInnerClass getInstance() {
return StaticInnerClassInstance.instance;
}
/*
* 私有化构造器,避免被外部调用
*/
private StaticInnerClass() {}
}
枚举实现单例模式
优点:
- 实现简单
- 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
缺点:
- 无延迟加载
代码实现
package designpatterns.singleton;
/**
* 枚举实现单例模式
* @author Hertter
* @since 2019年8月4日上午12:22:04
*/
public class EnumTypeTest {
public static void main(String[] args) {
// 测试单例模式
EnumType enumType1 = EnumType.INSTANCE;
EnumType enumType2 = EnumType.INSTANCE;
// 两者的地址相等,对外存在一个对象
System.out.println(enumType1 == enumType2);
}
}
enum EnumType {
/*
* 定义一个枚举的元素,它代表了单例的一个实例
*/
INSTANCE;
/*
* 单例可以有自己的操作
*/
public void operation() {
// 功能操作
}
}
如何选择合适的实现方式?
占用资源少,不需要延迟加载 ? 枚举式 好于 饿汉式
占用资源大,需要延迟加载 ? 静态内部类式 好于 懒汉式
总结
以上就是单例模式的小结,在面试的过程中,如果面试官要求手写一个单例模式的话,可以写一个饿汉一个懒汉和一个静态内部类方式,估计就没问题了,而且代码量都比较少。
强烈建议大家自己手敲一遍,加深印象!