首页 > 技术文章 > 单例模式

zhuoqingsen 2018-03-18 16:38 原文

  保证内存中只有一个实例


1,不能被new? ==> 私有构造方法 或者 abstract 类(不能生成对象实例)
2,不能被new,那如何产生自己的示例给调用者? ==> static方法
3,如果是并发的第一次被调用,那又怎样处理? ==> 同步锁

 

 

 懒汉模式写法

 

 

以上写法比较累赘把

 

饿汉模式写法

public class StarveSingleton {
    private static StarveSingleton s=new StarveSingleton();    
    private StarveSingleton(){}
    public static StarveSingleton getInstance(){        
        return s;
    }    
}

 

 

这种写法比较脱俗一点,没有用到同步锁,那么有人问,为什么不用静态块初始化呢?如果使用静态块初始化,静态块中不能抛异常Exception,只能抛错误Error,但是一旦有异常,类没法初始化,直接就是一个错误

 

饿汉式是线程安全的,在类被加载的同时就已经创建好一个静态的对象供系统使用,以后不再改变。
优点是无需关注多线程问题、写法简单明了、能用则用,
但如果类很大,譬如:需要同步的业务方法很复杂、很多,需要缓存很多其它对象,那么就得考虑效率问题,因为这个类一加载则把所有实例不管用不用一块创建。
单从资源利用效率角度来讲,这个比懒汉式稍差些。
从速度和反应时间角度来讲,则比懒汉式稍好些。

 

懒汉式是延时加载,是在需要的时候才创建对象。
优点是延时加载、缺点是应该用同步(想改进的话现在还是不可能,比如Double-Check)。
如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的,譬如 并发访问getInstance,判断都是null,new出2个实例。
最好用双锁,不要把同步加在方法上,非常影响性能
其实也可以不用同步、看你的需求了,多创建一两个无引用的废对象其实也没什么大不了。
而且,延时加载也延时不到哪里,因为,类被加载到虚拟机,不就是要继续调用getInstance嘛

 

最好方式:

 静态内部类单例模式--不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

public class ASD{
    //构造函数私有化
    private ASD(){}
    //内部类创建单例
    private static class ASDFactoryInstance{
        private static ASD instance= new ASD();
    }
    //延时创建
     public static ASDFactoryInstance getInstance(){
        return ASDFactoryInstance.instance;
     }
}

 

扩展 

关于单例模式被破坏

1.反射

2.反序列化

 

解决方案

1.在判断对象是否NULL的时候,抛出异常,反射就不会继续创建对象

2.只要在Singleton类中定义readResolve就可以解决该问题

 

 

单例模式和多线程有关系吗?

我们知道一个对象是可以同时被多个线程进行调用的,假如一个单例对象每次都只能有一个线程进行调用的话,就不会出现线程中的安全问题了。只有一个当一个对象加锁后,才能做到每次只能有一个线程进行处理。但是,加锁只能同步方法,不能同步类和属性变量。

在一个线程中,如果创建一个对象,会先在堆内存中开辟一个存储空间,该对象在创建的时候就会在栈内存中开辟空间,调用其构造方法、静态代码块等…当方法结束,会在栈内存中清除。一个线程在调用这个对象的方法的时候会在栈内存中创建一个空间存储对象方法。

同理,对于在一个线程加载单例模式对象的时候,它会在静态共享区获取单例对象,然后在调用对象方法(非静态)的时候会在自己的栈内存开辟一个空间,所以每个线程在调用同一个对象的方法的时候都会在它的栈内存中开辟一个独立的内存空间。

一个单例模式的方法可以同时被多个线程处理,多个线程如果不是同时处理一个对象的共有属性,则不会出现线程问题,即使是方法中的属性如果两个线程同时访问同一个方法的时候,如果这个方法中没有共有的属性,则不需要加锁,反之则把竞争访问的资源变量标识为private, 同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。

 

最后拓展一下多线程同步问题

  1. 如果只是读操作,没有写操作,则可以不用加锁,此种情形下,建议变量加上final关键字;
  2. 如果有写操作,但是变量的写操作跟当前的值无关联,且与其他的变量也无关联,则可考虑变量加上volatile关键字,同时写操作方法通过synchronized加锁;
  3. 如果有写操作,且写操作依赖变量的当前值(如:i++),则getXXX和写操作方法都要通过synchronized加锁。

 

推荐阅读