您的位置:首页 > 其它

浅谈设计模式之单例模式

2016-03-06 00:39 465 查看
  单例模式,可以说是众多设计模式中的最简单的一种的设计模式。因为它更易于理解和更易于掌握。

  一、单例模式最核心的思想:Ensure a class has only one instance, and provide  a global point of access to it.(为了确保一个类只有一个实例(对象),并且为这个对象提供一个全局访问点)

  二、个人理解:就是在整个程序运行的过程中该类的实例只会被创建一次,一旦被创建,以后都不能被创建一个新的实例也即是该类的实例是独一无二的,类似古代的君王独一无二,无可替代的。

 三、思考分析:我们紧紧围绕着它的思想找突破点,它的意思就是保持这个类只能有一个对象。

 那么我们如何去确保一个类只能创建一个对象了,也即是保证这个类的对象只能被创建一次???

 我们可以这样想:我们都知道要想创建一个对象,最直接最必须的方法就是通过这个构造方法来创建,然后直接通过new关键字就可以,也即是我们创建多次该类对象,就new几次。所以我们必须控制这种构造器泛滥使用,从而保证我们单例模式的类对象只能被创建一次。所以就索性断绝或者说屏蔽了外部直接通过new方式来泛滥或者说任意地创建多个

对象。所以就想到了:  

 1、控制单例模式下该类的构造方法权限(private 私有化),从而屏蔽了外部泛滥地创建该类的对象权利,切断所有外界创建该类对象途径或者说接口,(因为构造器是创建对象的唯一途径,构造方法加了private修饰词后,外部是没有权限创建该类的对象的)。-------------(关键点一:该类的构造器私有化)

但是这样的话问题来了,因为屏蔽了所有外部创建该类的对象途径,但是我们又需要创建一个唯一的对象实例,那怎么办呢??憋慌,既然外部不能创建,那么我们就从内部开始

创建。

2、既然是独一无二的,那么不是谁都能访问到的,就像古代的皇帝一样,独一无二的,并不是谁想见都能见到的。但是外部的人又必须去向皇帝报告一些事情,他只能间接地通过皇帝身边的人,内部人员间接把信息传达给皇帝。我们的单例模式也是如此外部是不能直接访问该类的对象,所以该类的唯一对象必须私有化(private),还有一个就是static静态的(为什么是static后面解释)外部是不能直接访问该对象的引用的,否则可以访问的话,那么这个对象的引用可以被任意的修改。所以需要给该对象的引用私有化-----(关键点二:设置一个私有的静态的该类对象的引用)

3、那么问题又来了,就是你好不容易在内部创建的一个该类的对象,还被设置成private外部不能访问,我们要能全局被访问???

 既然外不能访问的话,我们就直接在内部访问,并且提供一个方法(这个方法就相当于皇帝身边人),但是这个方法必须是全局访问的所以需要static静态的,(到了这里我想你应该明白了为什么该类的对象要静态,因为这个方法是静态的,静态的方法只能访问静态的变量),供外部访问,从而实现了外部间接访问这个对象。一个public权限的static静态方法(为了设置一个全局访问点)来返回这个已经在内部创建好的对象,也就是对于外部而言

暴露这个方法,供外部访问,这也是外部访问该对象的唯一途径(唯一一个访问点)。---------(关键点三:设置一个公有的静态的方法,访问内部对象,并把间接把

该内部对象返回给外部,从而使得外部要得到该对象,只能通过调用该方法,才能访问到该对象。)

四、总结:

1、该类的构造器私有化 

2、设置一个私有的静态的该类对象的引用

3、设置一个公有的静态的方法,访问内部对象,并把间接把该内部对象返回给外部,从而使得外部要得到该对象,只能通过调用该方法,才能访问到该对象。


五、两种的单例模式

       5.1、饿汉式单例模式

       特点:类加载的时候,就对这个类的对象进行创建。

public class HungrySingleton {//饿汉式的单例模式
private HungrySingleton(){}//关键点一:构造器私有化(屏蔽了外部直接使用new关键字泛滥地创建对象)
private static HungrySingleton singleton=new HungrySingleton();//关键点二:设置一个私有的静态的该类对象的引用(屏蔽了外部直接访问该对象的权限)
public static HungrySingleton createSingleton(){//关键点三:设置一个公有的静态的方法,访问内部对象,并把间接把该内部对象返回给外部,从而使得外部要得到该对象,只能通过调用该方法,才能访问到该对象。
return singleton;
}
}

      5.2、懒汉式单例模式

      特点:什么时候需要用到这个类的对象才开始去创建该类的对象

 

public class LazySingleton {//懒汉式单例模式
private LazySingleton(){}//关键点一:构造器私有化
private static LazySingleton singleton=null;//关键点二:该类的对象的静态,私有化
public static LazySingleton createSingleton(){
if (singleton==null) {//如果该对象还没创建,就开始创建
singleton=new LazySingleton();
}
return singleton;//如果已存在就直接返回
}
}


六、懒汉式单例模式的改进

为什么要对该单例要改进呢,不是挺好的吗,挺完美的吗?大家可以仔细观察一下上面的代码,是不是 少了什么,如果是在单线程的操作上面是没有问题,但是如果是多线程中就会存在线程同步的问题,如果同时有线程A和线程B同时调用该静态的createSingleton方法,那么if(singleton==null){}语句为真,那么此时线程A和线程B都会创建一个新的该类的对象,所以就不符合但到单例模式的思想。解决办法,就是保证线程A在调用createSingleton方法的时候,其他的线程不能访问该方法,所以需要给这个方法加锁

public class LazySingleton {//懒汉式单例模式
private LazySingleton(){}//关键点一:构造器私有化
private static LazySingleton singleton=null;//关键点二:该类的对象的静态,私有化
synchronized public static LazySingleton createSingleton(){
if (singleton==null) {//如果该对象还没创建,就开始创建
singleton=new LazySingleton();
}
return singleton;//如果已存在就直接返回
}
}<span style="font-size:18px;">
</span>

七、饿汉式单例模式与懒汉式单例模式区别

1、饿汉式单例模式是在单例类被加载的时候就创建该类的实例,而懒汉式的单例模式则是在第一次引用该类的实例才去创建的

2、从资源内存上看饿汉式单例模式更差点,但是从效率来看的话饿汉式更高,速度和反应时间更快

3、饿汉式在JAVA中很容易实现,但是在C++中却很难实现。GoF在提出单例模式的概念的时候,举例子就是懒汉式的单例模式,所以以至于

在JAVA中单例模式一般都是懒汉式的单例模式,其实实际上,就JAVA语言的特点来说的话,饿汉式的单例模式更合适,因为提高速度和效率,再者对于多线程的JAVA来说,饿汉式的单例模式就轻松避开了线程同步的问题

八、单例模式的优缺点:

1、单例模式的优点:

                                1.1、单例模式在内存中只有一个实例对象,减少了内存的开支,特别是一个对象需要频繁的创建、销毁,而且创建或销毁的性能又无法优化时可以考虑采用到哪例模式

                                1.2、由于单例模式只能创建一个实例对象,减少性能开销,当一个对象创建需要较多的资源,如读取配置,产生对其他对象的依赖的时候,可以考虑单例模式,然后用永久驻留内存方式来解决。

                               1.3、单例模式可以避免对资源的多重占用

                               1.4、单例模式可以在系统设置全局的访问点,优化和共享资源访问

2、单例模式的缺点:

                                2.1、无法创建子类,不利于扩展,扩展性差。

                               2.2、单例模式对测试不利。在测试过程中,并行的开发环境下,如果采用单例模式的类没有测完是不能进行其他的测试。

九、单例模式的应用:在一个系统中,如果要求一个类有且仅有一个实例,当出现多个实例时就会造成不良反应,则此时可以采用单例模式。

                                1、要求生成唯一的序列号环境

                                2、在整个项目中需要一个共享访问点或共享数据,例如,一个web页面的计数器,可以使用单例模式来保持计数器的值更新,并且能确保线程是安全的

                                3、创建一个对象需要消耗过多的资源,如访问IO,数据库资源等

                                4、需要定义大量的静态的变量和方法,也可以使用单例模式

十、单例模式使用的过程中应注意什么?(根据功能,单例模式的类分为两种:一种是有状态的单例类;一种是无状态的单例类)

有状态的单例类:它的对象一般是可变的,通常当做状态库使用,例如:给系统提供唯一的序列号

无状态的单例类:无状态的单例类是不变的,一般提供的是工具性的功能方法。例如IO操作,访问数据库资源

                               注意点一:单例类仅仅局限于一个JVM,所以当有多个JVM的分布式系统时,这个单例类就会被在多个JVM中实例化,造成多个单例类的对象出现,与单例模式的思想的不符合,若是无状态的单例类则不会有影响,因为无状态的单例类是不变的;但是,如果是有状态的单例类则会出现问题,例如,是给系统生成唯一的序列号,那么会造成系统的序列号不唯一。

                               注意点二:同一个JVM中会有多个类的加载器,当两个类加载器同时加载同一个类的时候,会出现单例类的两个实例,所以还是得尽量少使用有状态的单例类。

                             注意点三:在使用单例模式时,我们还需要去关注一点就是序列化(Serializable)和克隆(Cloneable)对实例的唯一性的影响。如果一个单例的类使用

实现Serializable和Cloneable接口,可能被反序列或克隆出一个新的实例,从而破坏了单例模式的思想,所以单例类一般不会去实现这两个接口

十一:应用

应用一:统计访问该单例类的次数

饿汉式的单例模式:

public class CountSingleton {
private CountSingleton(){}
private static CountSingleton mCountSingleton=new CountSingleton();
private int num=0;
public static CountSingleton getInstance(){
return mCountSingleton;
}
public synchronized int getCounts(){//synchronized实现线程同步
return ++num;
}
}
<pre class="java" name="code">
<p>package com.mikyou.example;</p><p>public class SingleDemo {</p><p> public static void main(String[] args) {
  CountThread threadA=new CountThread("A线程");
  CountThread threadB=new CountThread("B线程");
  threadA.start();
  threadB.start();
 }
}
class CountThread extends Thread{
 private String threadName;
 public CountThread (String name) {
  threadName=name;
 }
 @Override
 public void run() {
  CountSingleton singleton=CountSingleton.getInstance();
  for (int i = 0; i < 5; i++) {
   System.out.println(threadName+"--->第"+singleton.getCounts()+"次访问该单例类");
   try {
    sleep(1000);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }
}</p><pre class="java" name="code">package com.mikyou.example;

public class SingleDemo {

public static void main(String[] args) {
CountThread threadA=new CountThread("A线程");
CountThread threadB=new CountThread("B线程");
threadA.start();
threadB.start();
}
}
class CountThread extends Thread{
private String threadName;
public CountThread (String name) {
threadName=name;
}
@Override
public void run() {
CountSingleton singleton=CountSingleton.getInstance();
for (int i = 0; i < 5; i++) {
System.out.println(threadName+"--->第"+singleton.getCounts()+"次访问该单例类");
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}



运行结果:(虽然A、B两个线程运行的顺序不一样,但是它利用单例模式成功实现了在两个线程下,采用饿汉式单例模式每次只允许一个线程去调用该方法,所以进一步体现了饿汉式的单例模式的线程安全)



懒汉式的单例模式(无加锁)

package com.mikyou.example;

public class CountSingleton {
private CountSingleton(){}
private static CountSingleton mCountSingleton=null;
private int num=0;
public static CountSingleton getInstance(){//无加锁
if (mCountSingleton==null) {
mCountSingleton=new CountSingleton();
}
return mCountSingleton;
}
public synchronized int getCounts(){//synchronized实现线程同步
return ++num;
}
}
<pre class="java" name="code">package com.mikyou.example;

public class SingleDemo {

public static void main(String[] args) {
CountThread threadA=new CountThread("A线程");
CountThread threadB=new CountThread("B线程");
threadA.start();
threadB.start();
}
}
class CountThread extends Thread{
private String threadName;
public CountThread (String name) {
threadName=name;
}
@Override
public void run() {
CountSingleton singleton=CountSingleton.getInstance();
for (int i = 0; i < 5; i++) {
System.out.println(threadName+"--->第"+singleton.getCounts()+"次访问该单例类");
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}


运行结果:



到这里,设计模式中的单例模式,个人认为常见的问题都总结到了,如果还有哪里有缺陷,请大家多多指出,希望共同学习共同进步。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: