浅谈Java单例模式

来源:互联网 发布:程序员岗位 编辑:程序博客网 时间:2024/05/22 03:47

相信在设计模式中有一个经常提到的概念:单例模式,为什么它经常出现在面试话题中,因为它的应用场景十分广泛。

使用场景:

比如

  • 数据库连接池,作为数据库的缓存,避免频繁连接关闭数据库,
  • Java线程池,控制管理线程。
  • log4j日志记录,由始至终记录着运行日志。

定义:

保证系统中一个类只有一个实例,而且必须自己创建自己的唯一实例,该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。


创建单例模式的几种方式以及比较

1. 饿汉模式

/* 1. 饿汉模式: 2. 优点:线程安全 3. 缺陷:性能低/加载类就初始化单例/不适合需要外部传入参数配置的单例模式 */public class SingletonHungry {    private static final SingletonHungry instance = new SingletonHungry();    public static SingletonHungry getInstance() {        return instance;    }    public static void main(String[] args) {                SingletonHungry s1 = SingletonHungry.getInstance();        SingletonHungry s2 = SingletonHungry.getInstance();        System.out.print("饿汉模式实例对比:");        //true        System.out.println(s1.getInstance()==s2.getInstance());    }   }

由于饿汉模式在类内部创建实例,所以它是线程安全,正式它在类内部就静态加载,所以它不能从外部传入参数配置。
具体来看看懒汉模式.

2. 懒汉模式

package com.dd.code.singleton;/* * 懒汉模式 * 优点:简单/对比饿汉,加载此单例可以外部传入配置 * 缺陷:线程不安全 */public class SingletonLazy {    private static SingletonLazy instance;    /*       * 配置成员conf(假设必须传入conf该单例才可以加载)     *  这不能在类中优先初始化 private static final SingletonHungry instance = new SingletonHungry();     */    private static String conf;    //外部传入属性配置    public static void setConf(String conf) {        SingletonLazy.conf = conf;    }    public static SingletonLazy getInstance() {        if (instance == null) {            instance = new SingletonLazy();        }        return instance;    }    public static void main(String[] args) {        SingletonLazy.setConf("配置文件优先");        SingletonLazy s1 = SingletonLazy.getInstance();        SingletonLazy s2 = SingletonLazy.getInstance();        System.out.print("懒汉模式实例对比:");        //true        System.out.println(s1.getInstance()==s2.getInstance());    }}

对比饿汉模式,懒汉模式可以传入必要配置再手动实例化,但是由于手动实例化,则需要考虑线程安全问题。

3. 线程安全懒汉模式

/* *  线程安全懒汉加载 *  优点:线程安全/可以外部传配置 *  缺陷:代价较高,创建单例只需要第一次保证线程安全就好,不需要每次都同步 *  优化解决:SingletonDoubleCheck */public class SingletonThreadSafe {    private static SingletonThreadSafe instance;    //加了同步关键字synchronized    private synchronized SingletonThreadSafe getInstance() {        if (instance == null) {            instance = new SingletonThreadSafe();        }        return instance;    }    public static void main(String[] args) {        SingletonThreadSafe s1 = new SingletonThreadSafe();        SingletonThreadSafe s2 = new SingletonThreadSafe();        System.out.print("线程安全懒汉加载实例对比:");        //true        System.out.println(s1.getInstance()==s2.getInstance());    }}

加了同步关键字synchronized保证了线程安全,但是它的性能就降低了,而且其实创建单例只需要第一次保证线程安全就好,不需要每次都同步。

所以引入了新的优化,好像很厉害的双重锁检测模式

4. 双重锁检测单例

/* * 双重锁单例模式(较复杂) * 优点:可传入配置/对比SingletonThreadSafe性能优化,只有在实例为空(第一次实现同步) * 为什么要第二次判断instance是否为空,因为把synchronized放里层的话, * 有可能有多个线程进入了*临界区*,synchronized只能保证临界区每次由一个线程执行而已, * 二次检测可以让其他线程下次不初始化,防止冗余情况 */public class SingletonDoubleCheck {    /*     * volatile关键字:     * 要知道,instance = new SingletonDoubleCheck();不是原子性操作,     * 虽然volatile关键字不能保证原子性,但是可以禁止指令重排     * 保证了instance = new SingletonDoubleCheck() 这一行的有效执行顺序      */    private volatile static SingletonDoubleCheck instance;    private static SingletonDoubleCheck getInstance() {        if (instance == null) {            //非临界区            synchronized (SingletonDoubleCheck.class) {                //*临界区*                if (instance == null) {                    instance = new SingletonDoubleCheck();                }            }        }        return instance;    }    public static void main(String[] args) {        SingletonDoubleCheck s1,s2;        s1 = SingletonDoubleCheck.getInstance();        s2 = SingletonDoubleCheck.getInstance();        System.out.print("双重锁单例加载实例对比:");        //true        System.out.println(s1==s2);    }}

双重锁模式可以说是性能和线程安全的折中,它保证线程安全又保证不需要每次都控制同步,第一个判断if (instance == null)用来拦截已经创建的线程。
主要复杂的是从非临界区到临界区的情况,即当未创建实例的时候:要知道synchronized关键字其实只能保证每次一个线程执行修饰代码块,并不能保证只有一个线程,假设有超过一个线程进入临界区,此时如果线程一执行instance = new SingletonDoubleCheck(),则线程2下一次会根据第二个if (instance == null)判断是否再次创建实例,所以第二个if其实相当于一个flag标记,它巧妙的避免了多个线程创建多个实例


这种方式有点烧脑,官方推荐还有另一种创建方式,静态内部类模式,比较推荐使用的一种

5. 静态内部类

/* * 静态内部类  */public class SingletonNested {    private static class SingletonHolder{        public static final SingletonNested HOLDER_INSTANCE = new SingletonNested();    }    public static SingletonNested getInstance() {        return SingletonHolder.HOLDER_INSTANCE;    }    public static void main(String[] args) {        SingletonNested s1, s2;        s1 = SingletonNested.getInstance();        s2 = SingletonNested.getInstance();        System.out.print("静态内部类实例对比:");        //true        System.out.println(s1==s2);    }}

静态内部类创建单例的优点:

  1. 由jvm保证线程安全,不需要用到同步控制,性能较高;
  2. 因为区别于懒加载把instance作为静态内部成员,所以类加载时不会实例化instance 只有getInstance调用才会初始化。

参考链接:

  • http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/

  • http://blog.csdn.net/lovelion/article/details/7420888

原创粉丝点击