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

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

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

目录

一.什么是单例模式 

二.单例模式的实现思路

三.单例模式的优缺点

四.单例模式的不同写法

五.JDK种单例的例子

六.小结


一.什么是单例模式 

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

二.单例模式的实现思路

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

三.单例模式的优缺点

优点:

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

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

四.单例模式的不同写法

1.懒汉式

package com.yx.singleton;

/**
 * Singleton
 * 

初级原型-懒汉式 *

优点:用到时再初始化 *

缺点:并发下会产生多个实例 *

不推荐 * * @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
 * 

懒汉式优化,使用synchronized *

优点:节省空间, 用到的时候再创建实例对象,线程安全 *

缺点:所有线程的访问都会进行同步操作,影响性能 * * @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
 * 饿汉模式,实例化对象在对象声明的时候就进行实例化
 * 

优点:JVM层面的线程安全 *

缺点:创建之后如果不使用这个实例, 就造成了空间的浪费 * * @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
 * 枚举类单例模式
 *
 * 

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

缺点:所有的属性都必须在创建时指定, 也就意味着不能延迟加载; 并且使用枚举时占用的内存比静态变量的2倍还多 * * @author yx * @date 2019/11/24 15:09 */ public enum EnumSingleton { INSTANCE }

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

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

7.通过ThreadLocal实现单例模式

package com.yx.singleton;

/**
 * ThreadLocalSingleton
 * 通过ThreadLocal实现单例模式
 * 

不常见,性能也较低 * * @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 种双锁检验方式。

微信扫描关注更精彩

你可能感兴趣的:(Java,复习)