単例模式

来源:互联网 发布:excel怎么导入txt数据 编辑:程序博客网 时间:2024/05/03 14:34


1.  问题引出


在项目中如果需要保证一个类的实例具有全局唯一性,那这时候应该怎么办呢?这里比较方便的是使用単例模式,可以使我们的应用在运行期间对于特定的实例保证只有唯一一个实例,这样对于不同对象间的访问可以保证其访问的是同一个对象。

単例模式是我们常用的一种设计模式之一,在项目中也是经常使用到,使用的场景也普遍存在。比如一场篮球比赛中的篮球,一场足球比赛的足球,一个班级里面的正班长,一家公司里面的CEO等,这些都可以看作是一个単例的存在,当然,特殊的情况还是要令当别论,比如说篮球比赛中途换了另外一个篮球,这些都不是考虑的范围,我们考虑的是比赛的时候多名运动员都只是对一个球进行操作…

在软件开发中,単例模式的使用也是很普遍,一个项目的日志和输出系统,我想大多数人都会把它设计成全局唯一的,项目中使用了缓存模块,也必须保证全局唯一性,因为所有模块操作都是对一个缓存进行操作,还有就是系统使用的一些硬件资源,比如硬盘,鼠标等。


2.  使用介绍


当一个类必须确保在全局只有唯一实例时使用単例模式,这样可以在全局保持唯一的一个访问点,当然,你必须对外提供这个访问点。另外,使用単例模式从系统角度上讲还可以节省系统资源,避免平凡创建实例和销毁实例对系统资源的消耗。

単例模式的实现方式是利用构造函数私有化,在类外部并不能够调用构造函数,自然也就不能实例化这个类了。这样就把实例化的工作转移到了类的本身去实现,在实现単例功能的类里主要是提供一个对外的方法,去获取唯一实例的引用,这样就可以使用该引用进行交互了。

単例模式的uml类图如下:

 

可以看出,实现単例模式的方法可以很简单,本质上来说它就是一个类,只不过是该类把创建实例的过程转移到了类本身,并向外提供获取实例引用和相关的操作方法,外部可以根据获取到的实例引用操作对外的方法。

単例模式必须考虑一下几点:

1.      设计的単例类必须保证全局只能创建一个实例。

2.      単例类必须向外部提供这个唯一的实例

3.      単例类内部必须存在创建该实例的实现。

4.      多线程访问需要考虑线程安全。

 

3.  使用步骤


単例模式基本使用步骤如下:

1.      编写类代码,实现内部实例化的功能,并且对外提供实例化的引用。

public class Person {    private static Person person;    private String name;        private Person (){        this.name = "张三";    }    public void print() {        System.out.println("------> name: " + this.name);    }    public static Person getPerson() {        if (person == null) {            person = new Person();        }        return person;    }}


 

2.      外部使用単例类提供的方法获取唯一实例引用,通过引用访问内部资源。

Person per =Person.getPerson();per.print();


 

4.  实现方式


下面总结一下単例模式的几种实现方式:


4.1.  懒汉式


上面的例子是単例模式实现的简单方式,上面的例子没有考虑多线程的方式,如果多线程访问该类的方法,那么它并没有实现全局唯一的功能,因为多线程切换的时候有可能会创建不止一个Person的对象,这样単例的功能将完全没有体现其作用。一种解决的方法是使用synchronized关键字给方法同步,这样可以避免多线程使用该方法出现创建多个对象的问题。

public class Person {    private static Person person;    private String name;    private Person (){        this.name = "张三";    }    public void print() {        System.out.println("------> name: " + this.name);    }    public static synchronized Person getPerson() {        if (person == null) {            person = new Person();        }        return person;    }}


上面这种写法式単例模式懒汉式写法的实现,实质上就是延迟实例化该对象,它和开头的例子是一样的,只不过是增加了对方法的同步控制。

延迟实例化对象的主要原因有:

1.      在静态初始化对象时,刚开始的时候并没有足够多的信息来初始化对象,比如刚登陆时并没有获取到相关的信息,这些信息是在另外一个模块才能获取到的,这个时候初始化对象的信息就不完全。

2.      本来就不希望它在开始的时候初始化,这需要考虑到系统本身资源的使用问题,比如有些对象只有在特定模块才使用,这个时候我只需要在模块内进行实例化,那么开始的时候并不需要该对象的实例,这样可以很大程度上减小系统的开销,节省资源的利用。

 

4.2.  恶汉式

public class Person {    private static Person person = new Person();    private String name;    private Person (){        this.name = "张三";    }    public void print() {        System.out.println("------> name: " + this.name);    }    public static synchronized Person getPerson() {        return person;    }}


可以看出,恶汉式很懒汉式的区别在于恶汉式在类装载的时候就进行实例化了,实例化的引用通过一个静态变量保存,以后对getPerson()的调用都直接返回这个引用。这种方法可以避免多线程同步的问题,因为类装载的时候就初始化了一个唯一的实例,并保存了这个实例引用,后面对这个类getPerson()的调用都不会创建新的实例,这种写法是线程安全的。

 

4.3.  双重校验锁

public class Person {    private volatile static Person person;    private static Object obj = Person.class;    private Person () {    }    public static Person getPerson() {        if (person == null) {            synchronized (obj) {                if (person == null) {                    person = new Person();                }            }        }        return person;    }}


这里使用volatile关键字来通知编译器,使用person的时候都会从对象内存中取,而不是从当前工作内存取,这样做的好处就是可以保证每次取出的person都是内存中最新的,避免多线程切换时线程的引用的值不是最新更新的问题。但是这样也只是解决了获取的内存引用是最新的问题,还需要解决线程同步的问题。这里使用了synchronized来实现,通过同步功能实现,避免了多线程操作数据出现混乱的问题。在最外面的一层判断可以实现同步功能在创建对象的时候才启动,之后使用的时候都不会触发同步同能,这样可以提高程序运行效率。

 

4.4.  内部静态类

public class Person {    private String name;    private Person (){        this.name = "张三";    }    public void print() {        System.out.println("------> name: " + this.name);    }    private static class PersonHandler {        private static final Person person = new Person();    }    public static synchronized Person getPerson() {        return PersonHandler.person;    }}


使用这种方法与恶汉式最大的区别就是,这种方法可以延迟Person的实例化。恶汉式在最初类加载的时候就完成了Person的实例化,而使用内部静态类方法,由于PersonHandler开始没有被主动使用,所以并不会直接实例化Person。这种实现的好处可以查看前面讨论的延迟加载,包括初始化信息不足,资源优化的问题。

 

4.5.  枚举

public enum Person {    PERSON;    private String name;    Person() {        this.name = "张三";    }    public void print() {        System.out.println("------> name: " + this.name);    }}


JDK 1.5新增加了枚举类,使用枚举同样可以实现単例模式,这是Joshua Bloch的《Effective Java》所推荐的一种实现方式。使用这种方式的优点包括:

1.      自由序列化

2.      线程安全

3.      防止反射攻击创建实例

 

5.  总结


1.      实现単例模式的方式有:懒汉,恶汉,双重校验锁,内部静态类和枚举。

2.      使用単例模式需要考虑多线程同步问题,防止多线程创建不止一个単例类对象。

3.      根据实际情况决定是否对単例类进行延迟实例化。

 

 

 

 

 

 

0 0