Java设计模式之-单例模式

Java设计模式之-单例模式

  • 概念
  • 饿汉式
  • 懒汉式
  • 懒汉式单例模式的问题

概念

保证一个类仅有一个实例,并提供一个全局访问方法。构造方法私有化,类内部产生实例化对象并提供static方法获取实例化对象,不管外部怎么操作,都只有一个实例化对象。

通俗点讲单例模式就是:类的构造函数弄成private ,即不想让别人用new 方法来创建多个对象,可以在类里面先生成一个对象,然后写一个public static方法把这个对象return出去。

饿汉式

在系统加载类的时候会自动提供实例化对象。常见代码如下:

/**
 * 单例模式-饿汉式
 */
public class Singleton {
    //私有静态方法,防止被引用。为了使得类内部不能再次实例化,使用final
    private static final Singleton instance=new Singleton();
    //构造方法私有化,防止被实例化
    private Singleton(){    
    }
    public static Singleton getInstance(){
        return instance;
    }
    public void print(){
        System.out.println("Hello world");
    }
}
/**
* 调用测试
*/
public class test {
    public static void main(String[] args) {        
        Singleton instance=null;//声明对象
        instance=Singleton.getInstance();
        instance.print();
    }
}

注意:为什么使用static?在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。一句话:方便在没有创建对象的情况下来进行调用。

懒汉式

在系统加载类的时候不会自动提供实例化对象,而是在第一次使用的时候,进行实例化对象处理。常见代码如下:

/**
 * 单例模式-懒汉式
 */
public class Singleton2 {
    //私有静态方法,防止被引用,此处赋值为null,目的是实现延迟加载
    private static Singleton instance=null;
    //构造方法私有化,防止被实例化
    private Singleton(){    
    }
    public static Singleton getInstance(){
        if(instance==null){//第一次使用时才实例化对象
            instance=new Singleton();
        }
        return instance;
    }
    public void print(){
        System.out.println("Hello world");
    }
}

为什么需要单例模式?:如果一个类不需要产生重复对象,例如:一个类启动只需要一个类负责保存程序加载的数据信息。保证不管谁访问,都只产生一个对象。

反射与懒汉式单例模式

如果在多线程情况下,如上懒汉式单例模式代码就会造成不只产生一个实例化对象,这时候单例模式就不起作用了。这个问题的原因是多线程情况下,以下判断不同步,造成instance判断都为空,所以会产生不只一个实例化对象。

if(instance==null){//第一次使用时才实例化对象
    instance=new Singleton();
}

针对此问题,则需要进行同步处理,同步自然会想到synchronized关键字。修改如下所示:

//需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例
public static synchronized Singleton getInstance(){
    if(instance==null){
        instance=new Singleton();
    }
    return instance;
}

这个时候确实实现了同步,但是效率比较低,代价比较大。因为代码里面只有一个地方需要同步处理:instance实例化对象处理部分,在以上修改直接添加synchronized 显得草率了,所以这里需要更加合理的进行同步处理。

在懒汉式实现单例模式的代码中,有使用synchronized关键字来同步获取实例,保证单例的唯一性,但是上面的代码在每一次执行时都要进行同步和判断,无疑会拖慢速度,使用双重加锁机制正好可以解决这个问题,修改后整个代码如下所示。

/**
 * 单例模式-懒汉式
 */
public class Singleton {
    //私有静态方法,防止被引用,此处赋值为null,目的是实现延迟加载
    private static volatile Singleton instance=null;
    //构造方法私有化,防止被实例化
    private Singleton(){    
    }
    public static Singleton getInstance(){
    //需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例
        if(instance==null){//第一次使用时才实例化对象
            synchronized(Singleton.class){//反射机制
                if(instance==null){
                    instance=new Singleton();
                }
            }
        }
        return instance;
    }
    public void print(){
        System.out.println("Hello world");
    }
}

  双重加锁机制,这里的双重指的的双重判断,而加锁单指那个synchronized。为什么要进行双重判断,其实很简单,第一重判断,如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块,同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例。至于第二个判断,有点查遗补漏的意味在内。
  关于锁内部的第二重空判断的作用,当多个线程一起到达锁位置时,进行锁竞争,其中一个线程获取锁,如果是第一次进入则dl为null,会进行单例对象的创建,完成后释放锁,其他线程获取锁后就会被空判断拦截,直接返回已创建的单例对象。不论如何,使用了双重加锁机制后,程序的执行速度有了显著提升,不必每次都同步加锁。

volatile关键字的含义是:被其所修饰的变量的值不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存来实现,从而确保多个线程能正确的处理该变量。该关键字可能会屏蔽掉虚拟机中的一些代码优化,所以其运行效率可能不是很高,所以,一般情况下,并不建议使用双重加锁机制,酌情使用。

猜你喜欢

转载自blog.csdn.net/honeyyhq9988/article/details/80823010