java 设计模式 其一 单例模式

单例模式

转载出处(侵删)

http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html

有的时候,我们在全局中,为了不制造多个实例,避免一些资源问题的产生,只需要实例一次类的对象,用于:线程池、缓存、对话框、处理偏好设置和注册表对象、日志对象、驱动程序等等。


那我们要如何控制类只能实例化一个对象?
不妨看看我们平时是怎么使用类进行实例化的

public class Person{

}
Person p = new Person();

但是现在,说好的是要用单例模式,就要有所不同。
我们要达到的效果是:通过某种方法,只能实例化一次对象。那么根据以往所学到知识,static关键字可以满足这一目的(由于static修饰的方法,不依赖任何对象就可以访问,static是类的一部分)

//通过静态方法实现实例化对象
public class Person{

    private static Person p;

    public static Person getInstance(){
        return p = new Person();
    }

}

//这样我们创建对象的时候,通过类静态方法获取实例
Person p = Person.getInstance();

等等,好像有什么不对。
没错,这样,你依旧能够使用无参构造器去创建一个对象,或许我们可以修改无参构造器来禁止创建对象。

public class Person{
    //私有无参构造器禁止创建对象。
    private Person(){};

    private static Person p;

    public static Person getInstance(){
        return p = new Person();
    }
}

当然,如果这个对象是被创建了的,我们可以使用lazy loading,以防止在线程中创建了还再创建一次

//第一种 懒汉,线程不安全
public class Person{

    private Person(){};

    private static Person p;

    public static Person getInstance(){

        //存在对象时,禁止创建对象
        if(p==null)
            p = new Person()

        return p;
    }
}

初步就构建好了一个单例的例子了。


实际开发中,这往往不能满足我们的要求。如果遇到多线程怎么办?或许能上锁改装改装..

//第二种 懒汉,线程安全
public class Person{

    //无参构造器来禁止创建对象。
    private Person(){};

    private static Person p;

    public static synchronized Person getInstance(){
        if(p==null)
            p = new Person()

        return p;
    }
}

看起来貌似不错,但是实际上效率却很低,我们要拿到锁去调用方法。
有没有方法不加锁的呢?


在1.5 之后 我们可以通过下面的方式加锁。

//第三种 双重加锁校验
public class Person{

    private Person(){};

    private volatile  static Person p;

    //移除方法同步锁
    public static Person getInstance(){

        if(p==null)

        //如果为空,去拿锁,拿锁的过程需要等待,可能会在这个过程产生实例
            synchronized(Person.class){

            //拿锁之后,再次判断对象是否被创建
            if(p==null)
                p = new Person()
            }
        return p;
    }
}

这样总算大工告成了。这样就能安心在多线程中得到单例对象了。


什么?你觉得代码太烦。或许你能试试下面的方法

//第四种 饿汉
public class Person{

    private Person(){};

    //利用classloder机制 避免多线程的同步问题。
    private static Person p =new Person();

    public static Person getInstance(){
        return p;
    }
}

但是这种方法在类装载时进行实例化,虽然多少情况下是调用getInstance方法,不过不能排除有其他的静态的方法导致类装载,这个时候就没有达到 Lazy loading的效果。

PS:什么?你问我什么是“利用classloder机制 避免多线程的同步问题”?
好吧,根据《深入理解java虚拟机》中,大致是说,虚拟机会保证类的静态方法实例在多线程中正确地加锁、 同步,保证只有一个类能对其进行操作。所以可以使用上面的说法


所以有了下面使用静态内部类的方式

//第五种 静态内部类
public class Person{

    private Person(){};

    //静态内部类
    private static final PersonHolder{
        private static final Person pInstance =new Person();
    }

    public static Person getInstance(){
        return PersonHolder.pInstance;
    }
}

相比第四种 饿汉加载模式,这种即时在类被装载的时候,pInstance 也不一定初始化,因为PersonHolder没有被主动使用,只有显示的通过调用的getInstance方法,才会去装载 PersonHolder类,从而实例化 instance。


当然还有另外一种方式 《Effective java》 大佬 Josh Bloch 提倡,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒。(只是实际中很少人有这么写)

//第六种 枚举方式
class Resource{

}

public enum SomeThing {
    INSTANCE;
    private Resource instance;
    SomeThing() {
        instance = new Resource();
    }
    public Resource getInstance() {
        return instance;
    }
}

//当要实例化的时候,只需要
Resource r = SomeThing.INSTANCE.getInstance();

为什么说这样的单例能被保证?
枚举的构造方法是私有的,在我们在访问枚举实例时会执行构造方法,
同时每个枚举的实例都是 static finla 类型的,也就表明只能被实例化一次。在调用构造方法时,enum中的实例被保证实只会被例化一次。所以我们的INSTANCE也被保证实例化一次。
枚举还提供了序列化机制

public abstract class Enum<E extends Enum<E>> implements Comparable<E>,Serializable


有两个问题需要注意:
1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类 装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。

解决方式

private static Class getClass(String classname) throws ClassNotFoundException {     
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     

     if(classLoader == null)     
          classLoader = Singleton.class.getClassLoader();     

       return (classLoader.loadClass(classname));     
   }     
 }  

2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例

解决方式

public class Singleton implements java.io.Serializable {     
      public static Singleton INSTANCE = new Singleton();     

      protected Singleton() {     

      }     
      private Object readResolve() {     
          return INSTANCE;     
      }    
}   

最后

日常使用中,可以使用 第三种 和 第五种 ,第六种还没见人用过
(笔者代码看得太少的缘故吧,请多多指教啦 ╮(╯▽╰)╭ )

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值