JAVA设计模式---单例模式

来源:互联网 发布:jquery ajax json请求 编辑:程序博客网 时间:2024/06/16 19:29

参考:http://blog.csdn.net/jason0539/article/details/23297037/

定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机

从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

一、懒汉式单例

//懒汉式单例类.在第一次调用的时候实例化自己 public class Singleton {    private Singleton() {}    //私有构造方法,防止被实例化    private static Singleton single=null;    //静态工厂方法     public static Singleton getInstance() {         if (single == null) {               single = new Singleton();         }          return single;    }}
优点:实例在被使用的时候才被创建,可以节省系统资源,体现了延迟加载的思想。

缺点:由于系统刚启动时且未被外部调用时,实例没有创建;如果一时间有多个线程同时调用LazySingleton.getLazyInstance()方法很有可能会产生多个实例。

如何实现懒汉式单例模式的线程安全:

首先看一看多线程情况下懒汉式单例模式的问题:

package singleton2;  //懒汉模式  public class Singleton {        private static Singleton instance = null;            private Singleton(){}            public static Singleton getInstance(){          try {              if(instance==null){                  Thread.sleep(1000);//模拟延迟加载                  instance=new Singleton();              }          } catch (InterruptedException e) {              e.printStackTrace();          }          return instance;      }  } 
package singleton2;    public class ThreadA extends Thread{        @Override      public void run(){          System.out.println(Singleton.getInstance().hashCode());//根据实例对象哈希值是否相同来判断对象是否相同      }        }  
package singleton2;    public class Run {        public static void main(String[] args) {          ThreadA t1=new ThreadA();          ThreadA t2=new ThreadA();          ThreadA t3=new ThreadA();          t1.start();          t2.start();          t3.start();      }    }  
1321522194  2134502363  866891806  

哈希值不同,所以对象不同,即不是单例模式

解决方式:

public class Singleton {      private static volatile Singleton instance = null;      private Singleton(){}            public static Singleton getInstance(){          if(instance == null){              synchronized(Singleton.class){                  if(instance == null){                      instance = new Singleton();                  }              }          }          return instance;      }  }  
这里为什么要用volatile修饰instance?
原因:在于instance = new Singleton()的时候,在内存中实际上是分3步执行的:
1)分配对象的内存空间:memory = allocate();
2)初始化对象:ctorInstance(memory);
3)指向分配的地址:instance =memory
多线程在执行的时候,2 3可能发生重排序。即有可能线程A执行到第3步的时候,读取到instance不为null,就返回。实际上此时还未执行第二部即未初始化。
加上volatile就可以避免2 3步重排序来保证线程安全。
二、饿汉式单例

//饿汉式单例类.在类初始化时,已经自行实例化   public class Singleton1 {      private Singleton1() {}      private static final Singleton1 single = new Singleton1();      //静态工厂方法       public static Singleton1 getInstance() {          return single;      }  }  

优点:饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

缺点:在外部没有使用到该类的时候,该类的实例就创建了,若该类实例的创建比较消耗系统资源,并且外部一直没有调用该实例,那么这部分的系统资源的消耗是没有意义的。

三、懒汉式单例和饿汉式单例的区别

1、线程安全:

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的123,这三种实现在资源加载和性能方面有些区别。

 

2、资源加载和性能:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,

而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

至于123这三种实现又有些区别,

1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,

2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

0 0
原创粉丝点击