大话设计模式—单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

大话设计模式—单例模式_第1张图片

单例模式:

1、保证一个类仅有一个实例,并提供一个访问它的全局访问点;
2、当一个全局使用的类被频繁的创建和销毁;或者你要控制实例的数目,节省系统资源的时候,可以考虑使用单例模式;
3、实现:判断系统是否存在这个单例,如果存在则返回,否则,创建;
4、构造函数私有;

注意:

1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

单例模式的几种实现方式:

1、懒汉式(lazy loading)

package com.dfcDemo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/** * 第一次调用才初始化,避免内存浪费。 * 但是必须加锁 synchronized 才能保证单例,但加锁会影响效率。 * getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁) * @author lmb * */
public class SingletonLazy {

    private static Log logger =  LogFactory.getLog(SingletonLazy.class);
    private static SingletonLazy instance;

    private SingletonLazy(){
        logger.info("singleton is constructing");
    }

    public static synchronized SingletonLazy getInstance(){
        if(instance == null){
            instance = new SingletonLazy();//用到该类实例的时候再去初始化
        }
        return instance;
    }

    public static void main(String[] args) {
        logger.info("main方法开始执行---");
        //同时开启100个线程去调用getInstance()方法
        for (int i = 0; i <= 100; i++) {
            new Thread(new Runnable(){
                @Override
                public void run() {
                    SingletonLazy.getInstance();
                }
            }).start();
        }
        logger.info("main方法执行结束---");
    }
}

运行结果:

2016-03-17 11:16:04 INFO  com.dfcDemo.SingletonLazy  - main方法开始执行---
2016-03-17 11:16:04 INFO  com.dfcDemo.SingletonLazy  - singleton is constructing
2016-03-17 11:16:04 INFO  com.dfcDemo.SingletonLazy  - main方法执行结束---

2、饿汉式

package com.dfcDemo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/** * 类加载时就初始化,浪费内存 * @author lmb * */
public class SingletonHungry {

    private static Log logger =  LogFactory.getLog(SingletonHungry.class);
    private static SingletonHungry instance = new SingletonHungry();//首次加载该类的时候就初始化

    private SingletonHungry(){
        logger.info("singleton is constructing");
    }

    public static SingletonHungry getInstance(){
        return instance;
    }

    public static void main(String[] args) {
        logger.info("main方法开始执行---");
        //同时开启100个线程去调用getInstance()方法
        for (int i = 0; i <= 100; i++) {
            new Thread(new Runnable(){
                @Override
                public void run() {
                    SingletonHungry.getInstance();
                }
            }).start();
        }
        logger.info("main方法执行结束---");
    }
}

运行结果:

2016-03-17 11:16:23 INFO  com.dfcDemo.SingletonHungry  - singleton is constructing
2016-03-17 11:16:23 INFO  com.dfcDemo.SingletonHungry  - main方法开始执行---
2016-03-17 11:16:23 INFO  com.dfcDemo.SingletonHungry  - main方法执行结束---

3、双检锁/双重校验锁(DCL,即 double-checked locking)

实现原理:我们不让线程每次都加锁,而只是在实例未被创建的时候再加锁。同时也能保证多线程的安全,对于instance存在的情况就直接返回;当instance不存在,并且同时有两个线程调用getInstance()方法时,它们都可以通过第一重instance == null判断,然后由于synchronizd锁机制,两个线程只有一个能进入,另一个在外排队等候,必须要其中的一个进入并出来之后另一个才能进入。如果没有了第二重的instance == null的判断,则第一个线程创建了实例,第二个线程还是可以载继续创建实例的。

package com.dfcDemo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/** * 这种方式采用双锁机制,安全且在多线程情况下能保持高性能; * getInstance() 的性能对应用程序很关键 * @author lmb * */
public class SingletonDCL {

    private static Log logger =  LogFactory.getLog(SingletonDCL.class);
    /** * 用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新的值, * 即jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的; */
    private volatile static SingletonDCL instance;

    private SingletonDCL(){
        logger.info("singleton is constructing");
    }

    public static SingletonDCL getInstance(){
        if (instance == null) {
            synchronized (SingletonDCL.class) {
                if (instance == null) {
                    instance = new SingletonDCL();//用到该类实例的时候再去初始化
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        logger.info("main方法开始执行---");
        //同时开启100个线程去调用getInstance()方法
        for (int i = 0; i <= 100; i++) {
            new Thread(new Runnable(){
                @Override
                public void run() {
                    SingletonDCL.getInstance();
                }
            }).start();
        }
        logger.info("main方法执行结束---");
    }
}

运行结果:

2016-03-17 11:30:55 INFO  com.dfcDemo.SingletonDCL  - main方法开始执行---
2016-03-17 11:30:55 INFO  com.dfcDemo.SingletonDCL  - singleton is constructing
2016-03-17 11:30:55 INFO  com.dfcDemo.SingletonDCL  - main方法执行结束---

比较:

由于饿汉式,即静态初始化的方式,它是类一加载就实例化的对象,所以要提前占用系统资源。而懒汉式,又会面临着多线程访问的安全性问题,需要做双重锁定这样的处理才可以保证安全。所以,到底是用哪一种方式取决于实际的需求。

实用类与单例类比较:

实用类通常也会采用私有化的构造方法来避免其有实例,但它们还是有很多不同的:

1、实例类不保存状态,仅提供一些静态方法或静态属性,而单例类是有状态的;
2、实用类不能用于继承和多态,而单例类虽然实例唯一,但是可以有子类来继承;
3、实用类是一些方法属性的集合而单例却有着唯一的对象实例。

你可能感兴趣的:(设计模式,单例模式,Singleton,线程安全)