Java设计模式-单例模式(Singleton)

最近在一个项目中遇到问题了,设计中使用到了单例模式,但是因为多线程使用,出了一个bug,最后通过优化单例模式的写法将问题解决。

这里就来回顾下单例模式。

目录

一.什么是单例模式 

二.单例模式的实现思路

三.单例模式的优缺点

四.单例模式的不同写法

五.JDK种单例的例子

六.小结


一.什么是单例模式 

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。单例模式就是: 在程序运行期间, 某些类有且最多只有一个实例对象。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

二.单例模式的实现思路

   ① 静态化实例对象, 让实例对象与Class对象互相绑定, 通过Class类对象就可以直接访问;
   ② 私有化构造方法, 禁止通过构造方法创建多个实例;
   ③ 提供一个公共的静态方法, 用来返回这个类的唯一实例。

三.单例模式的优缺点

优点:

①、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
②、避免对资源的多重占用。
缺点:

①没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

四.单例模式的不同写法

1.懒汉式

package com.yx.singleton;

/**
 * Singleton
 * <p>初级原型-懒汉式</>
 * <p>优点:用到时再初始化</>
 * <p>缺点:并发下会产生多个实例</>
 * <p>不推荐</>
 *
 * @author yx
 * @date 2019/11/24 12:18
 */
public class Singleton {

    /**
     * 静态化实例对象
     */
    private static Singleton sInstance = null;

    /**
     * 私有构造方法
     */
    private Singleton() {
    }

    /**
     * 提供一个公共的静态方法, 用来返回这个类的唯一实例.
     *
     * @return 单例对象
     */
    public static Singleton getInstance() {
        if (sInstance == null) {
            sInstance = new Singleton();
        }
        return sInstance;
    }
}

上面这种写法,在并发环境下,会出现多个实例,线程不安全。

2.同步的懒汉式

package com.yx.singleton;

/**
 * LazySingleton
 * <p>懒汉式优化,使用synchronized</>
 * <p>优点:节省空间, 用到的时候再创建实例对象,线程安全</>
 * <p>缺点:所有线程的访问都会进行同步操作,影响性能</>
 *
 * @author yx
 * @date 2019/11/24 12:30
 */
public class LazySingleton {
    /**
     * 静态化实例对象
     */
    private static LazySingleton sInstance = null;

    /**
     * 私有构造方法
     */
    private LazySingleton() {
    }

    /**
     * 提供一个公共的静态方法, 用来返回这个类的唯一实例.
     *
     * @return 本类的实例
     */
    public static synchronized LazySingleton getInstance() {
        if (sInstance == null) {
            sInstance = new LazySingleton();
        }
        return sInstance;
    }
}
优点:节省空间, 用到的时候再创建实例对象,线程安全
缺点:所有线程的访问都会进行同步操作,影响性能

3.饿汉模式

package com.yx.singleton;

/**
 * HungrySingleton
 * 饿汉模式,实例化对象在对象声明的时候就进行实例化
 * <p>优点:JVM层面的线程安全</>
 * <p>缺点:创建之后如果不使用这个实例, 就造成了空间的浪费</>
 *
 * @author yx
 * @date 2019/11/24 12:21
 */
public class HungrySingleton {
    /**
     * 实例对象
     */
    private static HungrySingleton sInstance = new HungrySingleton();

    /**
     * 禁用构造方法
     */
    private HungrySingleton() {
    }

    /**
     * 获取单例对象, 直接返回已创建的实例
     *
     * @return 本类的实例
     */
    public static HungrySingleton getInstance() {
        return sInstance;
    }
}

优点:线程安全,代码简单

缺点:创建之后如果不使用这个实例, 就造成了空间的浪费

4.双锁检验

package com.yx.singleton;

/**
 * DoubleLockSingleton
 * 双锁检验
 *
 * @author yx
 * @date 2019/11/24 12:45
 */
public class DoubleLockSingleton {

    /**
     * 静态化实例对象 volatile防止指令重排
     */
    private static volatile DoubleLockSingleton sInstance;

    /**
     * 私有构造方法
     */
    private DoubleLockSingleton() {
    }

    /**
     * 获取单例对象, 直接返回已创建的实例
     *
     * @return 单例对象
     */
    public static DoubleLockSingleton getInstance() {
        if (sInstance == null) {
            synchronized (DoubleLockSingleton.class) {
                if (sInstance == null) {
                    sInstance = new DoubleLockSingleton();
                }
            }
        }
        return sInstance;
    }
}

优点:节省空间, 用到的时候再创建实例对象,线程安全,为null时同步创建,相对于2介绍的方式是一个优化
缺点:同步,影响性能

5.静态内部类模式

package com.yx.singleton;

/**
 * StaticInnerSingleton
 * 静态内部类模式, 也称作Singleton Holder(单持有者)模式
 * 线程安全, 懒惰模式的一种, 用到时再加载
 *
 * @author yx
 * @date 2019/11/24 12:51
 */
public class StaticInnerSingleton {
    /**
     * 禁用构造方法
     */
    private StaticInnerSingleton() {
    }

    /**
     * 通过静态内部类获取单例对象, 没有加锁, 线程安全, 并发性能高
     *
     * @return SingletonHolder.sInstance 内部类的实例
     */
    public static StaticInnerSingleton getInstance() {
        return SingletonHolder.sInstance;
    }

    /**
     * 静态内部类创建单例对象
     */
    private static class SingletonHolder {
        private static StaticInnerSingleton sInstance = new StaticInnerSingleton();
    }
}

优点:线程安全, 懒惰模式的一种, 用到时再加载,没有加锁, 线程安全, 用到时再加载, 并发行能高。

6.枚举方式

/**
 * EnumSingleton
 * 枚举类单例模式
 *
 * <p>优点:不需要考虑序列化的问题;不需要考虑反射的问题</>
 * <p>缺点:所有的属性都必须在创建时指定, 也就意味着不能延迟加载; 并且使用枚举时占用的内存比静态变量的2倍还多</>
 *
 * @author yx
 * @date 2019/11/24 15:09
 */
public enum EnumSingleton {
    INSTANCE
}

优点:不需要考虑序列化的问题;不需要考虑反射的问题。

缺点:所有的属性都必须在创建时指定, 也就意味着不能延迟加载; 并且使用枚举时占用的内存比静态变量的2倍还多

7.通过ThreadLocal实现单例模式

package com.yx.singleton;

/**
 * ThreadLocalSingleton
 * 通过ThreadLocal实现单例模式
 * <p>不常见,性能也较低</>
 *
 * @author yx
 * @date 2019/11/24 15:19
 */
public class ThreadLocalSingleton {
    /**
     * 如果 perThreadInstance.get() 返回一个非空值, 说明当前线程已经被同步了: 它要看到instance变量的初始化
     */
    private static ThreadLocal sPerThreadInstance = new ThreadLocal();
    /**
     * 静态实例
     */
    private static ThreadLocalSingleton sInstance = null;

    /**
     * 提供一个公共的静态方法, 用来返回这个类的唯一实例.
     *
     * @return 静态实例
     */
    public static ThreadLocalSingleton getInstance() {
        if (sPerThreadInstance.get() == null) {
            createInstance();
        }
        return sInstance;
    }

    /**
     * 创建实例
     */
    private static final void createInstance() {
        synchronized (ThreadLocalSingleton.class) {
            if (sInstance == null) {
                sInstance = new ThreadLocalSingleton();
            }
        }
        sPerThreadInstance.set(sPerThreadInstance);
    }

    public static void remove() {
        sPerThreadInstance.remove();
    }
}

线程安全,但实现代码复杂,性能偏低

五.JDK种单例的例子

(1) java.lang.Runtime类中的getRuntime()方法;
(2) java.awt.Toolkit类中的getDefaultToolkit()方法;
(3) java.awt.Desktop类中的getDesktop()方法;
(4) java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()方法。

六.小结

一般情况下,使用单例,建议使用饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用静态内部类模式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式,如果有其他特殊的需求,可以考虑使用第 4 种双锁检验方式。

微信扫描关注更精彩
发布了142 篇原创文章 · 获赞 258 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/conconbenben/article/details/103222919