- 概念
- 应用场景
- 优缺点
- 实现方式
- 面试
一、概念
一个类只有一个实例,并且提供一个访问该实例的全局访问点。
二、应用场景
1、最常见的比如说windows系统下的任务管理器
2、在项目中,读取配置文件的类
3、计数器
4、数据库连接池
5、Spring框架中,bean默认就是单例的
6、Spring MVC/struts框架中,控制器对象也是单例的
三、优缺点
优点:
1、因为单例模式只生成一个实例,减少了系统性能开销
2、当一个对象的产生需要较多资源时,可以通过在应用中启动时直接生产一个单例对象,然后永久驻留内存
3、单例模式可以在系统设置全局的访问点,优化环共享资源访问
缺点:
1、不适用于需要在不同用例场景下发生变化的对象
2、没有接口,不能继承,与单一职责冲突
四、实现方式
单例模式常见的实现方式主要有五种
1、饿汉式(线程安全,效率高,不能延迟加载)
2、懒汉式(线程安全,效率不高,可以延迟加载)
3、双重检测锁式(不建议使用)
4、静态内部类式(线程安全,调用效率高,可以延迟加载)
5、枚举单例(线程安全,调用效率高,不能延迟加载)
饿汉式(单例对象立即加载)
public class SingletonDemo{
private static SingleTonDemo st = new SingletonDemo();
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
return st;
}
}
public class Client{
public static void main(String[] args){
SingletonDemo st1 = SingletonDemo.getInstance();
SingletonDemo st2 = SingletonDemo.getInstance();
System.out.println(st1 == st2);//结果为true
}
}
在饿汉式单例模式中,static变量会在类加载时进行初始化,此时也不会出现多线程访问该对象的问题,虚拟机会保证只加载一次该类,所以不会发生并发访问的问题。
**缺点:**如果只是加载本类,而不调用getInstance()方法,则会造成资源浪费。
懒汉式(单例对象延迟加载)
public class SingletonDemo02{
private static SingletonDemo02 st;
private SingletonDemo02(){
}
public static synchronized SingletonDemo02 getInstance(){
if(st == null){
st = new SingletonDemo02();
}
return st;
}
}
在懒汉单例模式中,对象会在需要用到的时候再加载,资源利用率增高了
**缺点:**每次调用getInstance()方法时都要同步,并发效率较低
双重检测锁实现
public class SingletonDemo03 {
private static SingletonDemo03 st = null;
public static SingletonDemo03 getInstance(){
if (st == null){
SingletonDemo03 st2;
synchronized (SingletonDemo03.class){
st2 = st;
if (st2 == null){
synchronized (SingletonDemo03.class){
if (st2 == null){
st2 = new SingletonDemo03();
}
}
st = st2;
}
}
}
return st;
}
private SingletonDemo03(){
}
}
此模式将同步内容写入if内部,提高执行效率,不必每次获取对象以后都进行同步,只有第一次才同步
**缺点:**由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题,所以不建议使用
静态内部类实现(也是一种懒加载方式)
public class SingletonDemo04 {
private static class SingletonClassInstance{
private static final SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance(){
return SingletonClassInstance.instance;
}
private SingletonDemo04(){
}
}
外部类没有static属性,所以不会像饿汉式一样立即加载对象
只有在调用getInstance()时才会加载静态内部类,加载时线程是安全的,因为instance被static final所修饰,所以可以保证内存中只有一个这样的实例存在,并且只会被赋值一次,从而保证了线程安全性。
兼备了并发高效调用和延迟加载的优势
枚举实现
public enum SingletonDemo05 {
/** 定义一个枚举元素,代表Singleton的一个实例 */
INSTANCE;
/** 单例可以有自己的操作 */
public void singletonOperation(){
//需要进行的操作
}
}
实现简单
枚举本身就是单例模式,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞
**缺点:**不能延迟加载
五、面试
如何破解上述几种(不包含枚举实现)实现方式?怎么预防?
可以通过反射和序列化来破解
1、预防反射破解时,可以在构造方法中手动抛出异常控制
2、预防反序列化破解时,可以通过定义readResolve()防止获得不同对象
public class SingletonDemo implements Serializable {
private static SingletonDemo st;
private SingletonDemo() throws Exception {
if (st != null){
throw new Exception("只能创建一个对象");
}
}
public static synchronized SingletonDemo getInstance() throws Exception {
if (st == null){
st = new SingletonDemo();
}
return st;
}
/** 反序列化时,如果对象所在类定义了readResolve(),实际上是一种回调,定义返回哪个对象 */
private Object readResolve() throws ObjectStreamException {
return st;
}
}
下一篇博客将会讲解工厂模式,链接地址