1.单例模式【Singleton Pattern】:
设计模式:对问题行之有效的解决方式。其实它是一种思想,当人们在写代码的过程中,发现有些代码可提高效率,或者可复用,或者更灵活,在不断的实践中,提取出的一些代码,根据特点命名,就出现了设计模式。
1,单例设计模式。
解决的问题:就是可以保证一个类在内存中的对象唯一性。
UMl单例图:
饿汉式:这个类被加载的时候,静态变量会被初始化,类的对象就存在了。一加载,就创建这个唯一的对象。
class EagerSingle { /** * 饿汉式 * 这个类被加载的时候,静态变量会被初始化,类的对象就存在了 */ private static final EagerSingle s = new EagerSingle(); /** * 私有的默认的构造方法,避免外界通过构造方法new多个实例, * 构造方法是私有的,此类不能被继承 */ private EagerSingle(){ } /** * 静态工厂方法 * @return */ public static synchronized EagerSingle getInstance(){ return s; } }
懒汉式:类加载的时候,没有对象,只有调用getInstace()方法,对象才会被创建
class LazySingle{ private static LazySingle s1 = null; //私有的构造方法 private LazySingle(){ } /** * 加synchronized: * 原因: * 多个线程并发操作共享数据s1,因为有多条语句操作共享数据,会出现线程安全问题。 * 可能创建多个对象,存在线程安全的问题,则用同步锁, * 在方法上加同步锁,或者加同步代码块,该类所属的类字节码锁 * @return */ public static synchronized LazySingle getInstance(){ synchronized (LazySingle.class) { if(s1 != null){ //--A //--B //new 了不只一个对象 s1 = new LazySingle(); } } return s1; } }
public class Single{ public static void main(String[] args) { // TODO Auto-generated method stub LazySingle ls1 = LazySingle.getInstance(); LazySingle ls2 = LazySingle.getInstance(); System.out.println(ls1==ls2); } }
输出结果为:true。说明引用ls1和ls2 指向同一个对象,地址相同,堆内存中只存在一个对象。
单例设计模式的小应用:class Test{ private int num; private static final Test t = new Test(); private Test(){} public static synchronized Test getInstance(){ return t; } public int getNum(){ return num; } public void setNum(int num){ this.num = num; } } public class Single{ public static void main(String[] args) {
Test t1 = Test.getInstance(); Test t2 = Test.getInstance(); t1.setNum(10); t2.setNum(20); System.out.println(t1.getNum()); System.out.println(t2.getNum()); } }
输出结果是:20,20。而不是10,20,因为t1和t2指向同一个对象,t2.setNum(20)后面执行,对象的值被修改为20,所以都输出20.
2.单例模式的实现过程:
1.定义一个这个A类类型的静态变量 a,private static A a = null;
2.private + 类名的构造函数,必须是私有的,是保证这个类不能被其它类new出来。
Private A{}
3.对外提供一个一个public static A getInstance()的方法,其它类只能通过这个方法来得到这个类A的实例
4.通过new在本类中,将创建的对象返回
3. 加同步和不加同步的比较
方法二比方法一更加通用,更加完美,为什么呢?
因为方法一存在风险,方法一是线程不安全的,在一个B/S项目中,每个HTTP Request请求到J2EE的容器后就创建了一个线程,每个线程创建一个单例对象怎么办?假设这时候我们用方法一,大家看黄色部分,假如这会儿有两个线程A,B, 线程A执行到This.a = new A();在堆上正在申请内存空间,可能需要0.001微秒,就在这0.001微秒之内,线程B执行到 If (a == null),你说这个判断条件是true还是false呢?是true,因为线程A在堆上正在分配内存空间,还没有创建出实例来。那么,线程B也往下走,内存中就有两个a实例了,看看是不是出问题了?如果你这个单例是去拿一个序列号或者创建一个信号资源的时候,会怎么样?业务逻辑混乱!
数据一致性校验失败!最重要的是你从代码上还看不出什么问题,这才是最要命的!因为这种情况基本上你是重现不了的,不寒而栗吧,那怎么修改?用方法二,方法而通过final和synchronized 两个关键字解决了以上假设的问题!synchronized 用来解决线程不安全的问题。
4. 单例模式的优缺点:
优点:
1.减少了内存的开支,特别是一个对象需要频繁的创建、销毁时,而且创建和销毁的性能又无法优化,单利模式的优势就非常明显。
2.减少了系统的性能开销,当一个对象的产生需要比较多的资源的时候,如读取配置、产生其它依赖对象时,则可以在应用启动时直接产生一个单例对象,然后永久驻留在内存的方式来解决(在java EE采用单例模式时需要注意JVM垃圾回收机制)
缺点:1..单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
2.单例模式对测试是不利的。
5. 最佳实践:
单例模式应用非常广泛,如在Spring中,每个Bean默认就是单例singleton的,这样做的优点是Spring容器可以管理这些bean的生命周期,决定什么时候创建出来,什么时候销毁,销毁的时候如何处理,如果采用非单例模式(Prototype),则bean初始化的管理交由J2EE容器,Spring容器不在跟踪管理Bean的生命周期。(自己也在学Spring中用过)
更全面的参考文章:https://coolshell.cn/articles/265.html