java 设计模式 其一 单例模式

来源:互联网 发布:centos 五笔输入法 编辑:程序博客网 时间:2024/05/19 00:11

单例模式

转载出处(侵删)

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;           }    }   

最后

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

原创粉丝点击