什么是单例模式?
一.单例模式的概述:
单例模式,顾名思义就是一个类只有一个实例,并且类负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
为什么使用单例模式而不使用静态方法?
从面向对象的角度讲:
虽然都能实现目的,但是他们一个是基于对象,一个是面向对象的,就像我们不面相对象也能解决问题一样,面相对象的代码提供一个更好的编程思想。
如果一个方法和他所在类的实例对象无关,那么它就应该是静态的,反之他就应该是非静态的。如果我们确实应该使用非静态的方法,但是在创建类时又确实只需要维护一份实例时,就需要用单例模式了。
比如说我们在系统运行时候,就需要加载一些配置和属性,这些配置和属性是一定存在了,又是公共的,同时需要在整个生命周期中都存在,所以只需要一份就行,这个时候如果需要我再需要的时候new一个,再给他分配值,显然是浪费内存并且再赋值没什么意义,所以这个时候我们就需要单例模式或静态方法去维持一份且仅这一份拷贝,但此时这些配置和属性又是通过面向对象的编码方式得到的,我们就应该使用单例模式,或者不是面向对象的,但他本身的属性应该是面对对象的,我们使用静态方法虽然能同样解决问题,但是最好的解决方案也应该是使用单例模式。
从功能上讲:单例模式可以控制单例数量;可以进行有意义的派生;对实例的创建有更自由的控制;
二.单例模式的5种写法:
-
饿汉式
-
简单懒汉式(在方法加锁)
-
DCL双重检测加锁(进阶懒汉式)
-
静态内部类实现懒汉式(最推荐写法)
-
枚举方式(最安全、简洁写法)
写单例模式的代码其实可以简单的分为三步:
-
将构造函数私有化
-
在类的内部创建实例
-
提供获取唯一实例的方法
2.1饿汉式
1 public class model1 { 2 //1.将构造函数私有化,不可以通过new的方式来创建对象 3 private model1(){}; 4 //2.在类的内部创建实例 5 private static model1 model1 = new model1(); 6 //3.提供获取唯一实例的方法 7 public static model1 getLl(){ 8 return model1; 9 } 10 }
这种方法一上来就创建对象,如果一直用不到则造成资源浪费
2.2简单懒汉式
1 public class model2 { 2 //1.将构造函数私有化,不可以通过new的方式来创建对象 3 private model2(){}; 4 //2.先不创建对象,等用到的时候再创建 5 private static model2 model2 = null; 6 //3.提供获取唯一实例的方法,当这个方法被调用,说明用到了 7 public static model2 getLl(){ 8 //如果model2还没有别创建,则创建,如果已经有了,直接返回 9 if (model2 == null){ 10 model2=new model2(); 11 } 12 return model2; 13 } 14 }
这种方法是在用到的时候再创建对象,但需要注意的是,这种方法在多线程环境下需要加锁,如下:
1 public class model2 { 2 //1.将构造函数私有化,不可以通过new的方式来创建对象 3 private model2(){}; 4 //2.先不创建对象,等用到的时候再创建 5 private static model2 model2 = null; 6 //3.提供获取唯一实例的方法,当这个方法被调用,说明用到了 7 public static synchronized model2 getLl(){ 8 //如果model2还没有别创建,则创建,如果已经有了,直接返回 9 if (model2 == null){ 10 model2=new model2(); 11 } 12 return model2; 13 } 14 }
2.3DCL双重检测加锁
如果采用上面的方法,在方法上加锁,在多线程环境下性能比较低,所以将锁的范围降低
1 public class model2 { 2 //1.将构造函数私有化,不可以通过new的方式来创建对象 3 private model2(){}; 4 //2.先不创建对象,等用到的时候再创建 5 private static model2 model2 = null; 6 //3.提供获取唯一实例的方法,当这个方法被调用,说明用到了 7 public static model2 getLl(){ 8 //如果model2还没有别创建,则创建,如果已经有了,直接返回 9 if (model2 == null){ 10 synchronized(model2.getClass()){ 11 model2=new model2(); 12 } 13 } 14 return model2; 15 } 16 }
将范围降低后会出现一个问题:
线程A和线程B同时调用getLl()方法,他们同时判断model2==null
,进入了if代码块了
此时线程A得到CPU的控制权-->进入同步代码块-->创建对象-->返回对象
线程A完成了以后,此时线程B得到了CPU的控制权。同样是-->进入同步代码块-->创建对象-->返回对象
很明显的是:该类返回的不是一个实例,所以上面的代码是不行的!
解决方法是在进入同步代码块时在判断一下对象是否存在
1 public class model2 { 2 //1.将构造函数私有化,不可以通过new的方式来创建对象 3 private model2(){}; 4 //2.先不创建对象,等用到的时候再创建 5 private static model2 model2 = null; 6 //3.提供获取唯一实例的方法,当这个方法被调用,说明用到了 7 public static model2 getLl(){ 8 //如果model2还没有别创建,则创建,如果已经有了,直接返回 9 if (model2 == null){ 10 synchronized(model2.getClass()){ 11 if (model2==null){ 12 model2=new model2(); 13 } 14 } 15 } 16 return model2; 17 } 18 }
最后还需要在对象上加volatile关键字,防止出现重排序问题
1 public class model2 { 2 //1.将构造函数私有化,不可以通过new的方式来创建对象 3 private model2(){}; 4 //2.先不创建对象,等用到的时候再创建 5 private static volatile model2 model2 = null; 6 //3.提供获取唯一实例的方法,当这个方法被调用,说明用到了 7 public static model2 getLl(){ 8 //如果model2还没有别创建,则创建,如果已经有了,直接返回 9 if (model2 == null){ 10 synchronized(model2.getClass()){ 11 if (model2==null){ 12 model2=new model2(); 13 } 14 } 15 } 16 return model2; 17 } 18 }
2.4静态内部类实现懒汉式
1 public class model3 { 2 private model3(){}; 3 //使用内部类的方式来实现懒加载 4 private static class LazyHolder{ 5 //创建单例对象 6 private static final model3 INSTANCE=new model3(); 7 } 8 public static final model3 getInstance(){ 9 return LazyHolder.INSTANCE; 10 } 11 }
2.5枚举方式
1 public enum model4 { 2 MODEL_4, 3 }
这种方法简单,可以防止多次实例,也是比较推荐的一种.