1 单例模式的使用场景
(1)当创建一个对象所占用的资源很多,但同时又需要使用到该对象
(2)当堆系统内的资源要求统一读写时,比如读写的配置信息,此时必须要求创建的实例信息相同
(3)当有多个实例可能会引起程序逻辑错误时
总结:单例模式适用于只需要创建一个实例对象,程序全部使用同一个实例对象
2 实现方法
根据使用场景提炼出要点:
(1)某个类只能有一个实例
(2)必须要自行创建实例
(3)必须向整个系统提供这个实例
实现方法:
(1)只提供私有的构造方法
(2)含有一个该类的静态私有对象
(3)要提供一个静态的公用方法用于获取、创建私有对象
根据上面的描述,提供了两种实现单例模式的方法分别为饿汉式和懒汉式
3 饿汉式
该模式在创建类的时候就实例化,这样在获取的时候就可以有很快的效率但因为该类的实例化对象一直存在,所以占用的空间还是比较大的
//饿汉式:创建对象实例的时候直接初始化 空间换时间 public class SingletonOne { //1、创建类中私有构造 private SingletonOne(){ } //2、创建该类型的私有静态实例 private static SingletonOne instance = new SingletonOne(); //3、创建公有静态方法返回静态实例对象 public static SingletonOne getInstance(){ return instance; } }
在使用时可以采用: SingletonOne one =SingletonOne.getInstance(); 获取实例对象
4 懒汉式
与饿汉模式不同,是在第一次要使用该对象实例时才进行初始化,创建对象实例,这样第一使用时会花费时间,但节省了大量空间
//懒汉式:类内实例对象创建时并不直接初始化,直到第一次调用get方法时,才完成初始化操作 //时间换空间 public class SingletonTwo { //1、创建私有构造方法 private SingletonTwo(){ } //2、创建静态的该类实例对象 private static SingletonTwo instance=null; //3、创建开放的静态方法提供实例对象 public static SingletonTwo getInstance(){ if(instance==null) instance=new SingletonTwo(); return instance; } }
注:
懒汉式在多线程时可能会创建出多个多个对象,一个简单的方法就是加锁:
//懒汉式:类内实例对象创建时并不直接初始化,直到第一次调用get方法时,才完成初始化操作 //时间换空间 public class SingletonTwo { //1、创建私有构造方法 private SingletonTwo(){ } //2、创建静态的该类实例对象 private static SingletonTwo instance=null; //3、创建开放的静态方法提供实例对象 public synchronized static SingletonTwo getInstance(){ if(instance==null) instance=new SingletonTwo(); return instance; } }
在三步时加上了synchronized关键字,但当有static关键字时锁定其实是整个类而不是这个方法,这样锁的范围就太大了并且上锁开锁也比较消耗时间,因此有一种更为简便的方法:双重检查
public class LazyDoubleCheckSingleton { private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; private LazyDoubleCheckSingleton(){ } public static LazyDoubleCheckSingleton getInstance(){ if(lazyDoubleCheckSingleton == null){ //这个锁的范围和在static上一样的,但因为有了一个判断因此可以提高效率 synchronized (LazyDoubleCheckSingleton.class){ if(lazyDoubleCheckSingleton == null){ lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); } } } return lazyDoubleCheckSingleton; } }
上面的代码只是简单的优化,但又会引出一个新的问题:重排序:在单线程中一般情况下new一个对象在上面代码中即new LazyDoubleCheckSingleton();
是按照1 2 3 4 的步骤进行的,但有时2 3 的顺序也会互换这样可以提高效率,但是这样在多线程中就会遇到麻烦。
下图是上面代码中多线程的例子:
当线程0重排序时此时线程1会判断不为空,那么线程1便会访问对象,当对象此时还没有初始化因此便会报错。
解决方法1:禁止重排序,只需要在声明对象时加上一个关键字:volatile
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
解决方法2:允许重排序,但不允许其他线程看到重排序
public class StaticInnerClassSingleton { //私有构造方法 private StaticInnerClassSingleton(){ } //对于静态内部类Jvm有一个初始化锁 private static class InnerClass{ private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } public static StaticInnerClassSingleton getInstance(){ return InnerClass.staticInnerClassSingleton; } }
5 单例模式的优缺点
优点:
(1)在内存中只有一个对象节省了内存空间
(2)避免了频繁的创建销毁对象,提高了性能
(3)避免对共享资源的多重占用
缺点:
(1)后期扩展比较困难
(2)如果实例化后的对象长期不利用,系统将默认为垃圾进行回收,造成对象状态丢失
6 序列化
这些部分还没有看,因为涉及到的知识大多不会。
7 注解
0