设计模式之单例模式(Java)

来源:互联网 发布:县医院 网络图片图 编辑:程序博客网 时间:2024/06/06 19:13

设计模式之单例模式实现

单例模式是确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免多地同时修改状态。单例模式有以下特点:1、单例类只能有一个实例。2、单例类必须自己创建自己的唯一实例。3、单例类必须给所有其他对象提供这一实例。

  Java 编写单例模式有七种写法
第一种 懒汉式单例

package com.xiaokai.constructor.singleton;/** * Created by Administrator on 2016/8/4. */public class Singleton1 {    //私有静态实例(全局共享)    private static Singleton1 instance;    //私有构造器(外部不能实例化)    private Singleton1() {    }    //获取单例    public static Singleton1 getInstance() {        //如果没有实例化,先实例化返回        if (instance == null) {            instance = new Singleton1();        }        return instance;    }}
单元测试类
package com.xiaokai.constructor.singleton;import org.junit.Assert;import org.junit.Test;/** * Created by Administrator on 2016/8/4. */public class Singleton1Test {    @Test    public void getInstance() throws Exception {        Singleton1 singleton1 = Singleton1.getInstance();        Singleton1 singleton11 = Singleton1.getInstance();        Assert.assertSame(singleton1, singleton1);    }}
说明:此懒汉式单例在多线程不能正常工作(非线程安全),并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全。

1、在getInstance方法上加同步

//在获取单例方法上加锁public static synchronized Singleton1 getInstance() {         if (single == null) {               single = new Singleton1();         }          return single;}
这种写法虽然能够在多线程中很好的工作,但是,他的效率很低,因为单例已经存在的情况下是不需要同步的。

2、双重检查锁定

//加锁前先判单例是否已经存在,只有在不存的时候加锁实例化出来一个单例public static Singleton1 getInstance() {        if (singleton == null) {        //注意是类级锁              synchronized (Singleton1.class) {                 if (singleton == null) {                    singleton = new Singleton1();                }              }          }          return singleton;     }
注意:双重检查锁定在JDK1.5之后,才能够正常达到单例效果。

3、使用静态内部类实现单例模式(推荐)

public class Singleton {     //定义私有静态内部类     private static class SingletonHolder {         //加载时实例化单例,static final类型       private static final Singleton INSTANCE = new Singleton();      }    //私有构造器      private Singleton (){}    //通过调用getInstance实例化静态内部类,返回静态内部类加载时实例化的单例      public static final Singleton getInstance() {         return SingletonHolder.INSTANCE;      }  }  
内部类方式既实现了线程安全,又避免了同步带来的性能影响。这种方式利用了classloder的机制来保证初始化instance时只有一个线程,这种方式情况下,Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显式通过调用getInstance方法时,才会显式装载SingletonHolder类,从而实例化instance。如果实例化instance很消耗资源,让他延迟加载是很有帮助的。

第二种 饿汉式单例

package com.xiaokai.constructor.singleton;/** * Created by Administrator on 2016/8/4. */public class Singleton2 {    private Singleton2() {    }    //在类初始化时,实例化单例    private static final Singleton2 instance = new Singleton2();    //静态工厂方法    public static Singleton2 getInstance() {        return instance;    }}
单元测试类
package com.xiaokai.constructor.singleton;import org.junit.Assert;import org.junit.Test;import static org.junit.Assert.*;/** * Created by Administrator on 2016/8/4. */public class Singleton2Test {    @Test    public void getInstance() throws Exception {        Singleton2 singleton1 = Singleton2.getInstance();        Singleton2 singleton11 = Singleton2.getInstance();        Assert.assertSame(singleton1, singleton1);    }}
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

第三种 枚举式单例(强烈推荐)

package com.xiaokai.constructor.singleton;/** * Created by Administrator on 2016/8/4. */public enum Singleton3 {    INSTANCE;    public void whateverMethod() {    }}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。有两个问题需要注意: 1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类  装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。 2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。解决第一个问题的方法是重写Object的getClass方法。
    private static Class getClass(String classname)            throws ClassNotFoundException {        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();        //先判空,再加载单例        if (classLoader == null)            classLoader = Singleton3.class.getClassLoader();        return (classLoader.loadClass(classname));    }
解决第二个问题的方法是实现序列化接口后重写readResolve方法。
public class Singleton implements java.io.Serializable {    public static Singleton INSTANCE = new Singleton();    protected Singleton() {    }    private Object readResolve() {        return INSTANCE;    }}

总结

三类:懒汉(懒汉又有双重校验锁,静态内部类),饿汉,枚举。懒汉:需要加锁才能实现多线程同步,但是效率会降低。优点是延时加载。双重校验锁:麻烦,在当前Java内存模型中不一定都管用,某些平台和编译器甚至是错误的,在JDK1.5之后,才能够正常达到单例效果。。静态内部类:延迟加载,减少内存开销。因为用到的时候才加载,避免了静态field在单例类加载时即进入到堆内存的permanent代而永远得不到回收的缺点(大多数垃圾回收算法是这样)。饿汉:因为加载类的时候就创建实例,所以线程安全(多个ClassLoader存在时例外)。缺点是不能延时加载。枚举:很好,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。但是失去了类的一些特性,没有延迟加载。
1 0
原创粉丝点击