设计模式之单例模式

本文详细介绍了单例模式的定义及其实现方式,包括非线程安全实现、线程安全实现、双重校验锁、静态内部类等,并分析了每种方式的特点及优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

类图

单例模式

具体实现

1、非线程安全实现
package com.yrs.singleton;

/**
 * @Author: yangrusheng
 * @Description: "懒汉式"实现,非线程安全的单例模式实现。
 * @Date: Created in 9:21 2018/7/5
 * @Modified By:
 */
public class NonThreadSecuritySingleton {

    private static NonThreadSecuritySingleton singleton = null;

    private NonThreadSecuritySingleton() {

    }

    /**
     * 当线程一进入该方法,if语句判断singleton是null,然后进行实例化。在还没有实例化完时,线程二进入该方法,
     * 这时singleton依然是null,线程二也会实例化一个新的对象,就会造成有多个对象,因此线程不安全。
     * @return
     */
    public static NonThreadSecuritySingleton getNonThreadSecuritySingleton() {
        if (singleton == null) {
            singleton = new NonThreadSecuritySingleton();
        }
        return singleton;
    }

}
2、线程安全的”懒汉式”实现:
package com.yrs.singleton;

/**
 * @Author: yangrusheng
 * @Description: 线程安全的"懒汉式"单例模式实现
 * @Date: Created in 13:54 2018/7/5
 * @Modified By:
 */
public class SynchronizedMethodSingleton {

    private static SynchronizedMethodSingleton singleton = null;

    private SynchronizedMethodSingleton() {
        // 防止通过反射的方式,调用构造方法实例化对象。
        if (singleton == null) {
            singleton = this;
        } else {
            throw new IllegalStateException("Already initialized.");
        }
    }

    /**
     * 在方法上加锁,能够保证同一时间只有一个线程访问,能够保证线程安全,但它的一个致命的缺点是性能问题。当singleton不为
     * null时,也需要等待其他线程释放锁后才能访问getSingleton方法。对该方法的访问将变成串行,而且获取锁和释放锁都需要
     * 一定的开销,因此会出现性能问题。改进方案双重校验锁。
     * @return
     */
    public synchronized static SynchronizedMethodSingleton getSingleton() {
        if (singleton == null) {
            singleton = new SynchronizedMethodSingleton();
        }
        return singleton;
    }
}
3、双重校验锁方式:
package com.yrs.singleton;

/**
 * @Author: yangrusheng
 * @Description: 双重校验锁方式,线程安全的单例模式实现。
 * @Date: Created in 17:23 2018/7/5
 * @Modified By:
 */
public class DoubleCheckLockSingleton {

    // volatile关键字保证singleton = new DoubleCheckLockSingleton();语句在编译时不被指令重排序优化:
    // 1、给 instance 分配内存    2、调用 Singleton 的构造函数来初始化成员变量
    // 3、将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
    // 会存在1-3-2的执行顺序,线程2在线程1进行到3时进入getSingleton方法中,就会得到一个未初始化成员变量的对象,之后的
    // 操作会出现问题。

    private volatile static DoubleCheckLockSingleton singleton;

    //私有构造方法确保不被通过new实例化出对象
    private DoubleCheckLockSingleton() {
        // 防止通过反射的方式,调用构造方法实例化对象。
        if (singleton == null) {
            singleton = this;
        } else {
            throw new IllegalStateException("Already initialized.");
        }
    }

    /**
     * 双重校验是否为null的原因是:多个线程同时通过了第一个if语句,线程1获得锁,进行初始化后singleton不为null,线程1
     * 释放锁,线程2获得锁后,如果没有再次校验singleton为null的话,线程2将会创建第二个DoubleCheckLockSingleton对象,就
     * 会造成线程不安全。
     * @return
     */
    public static DoubleCheckLockSingleton getSingleton() {
        // local variable increases performance by 25 percent
        // Joshua Bloch "Effective Java, Second Edition", p. 283-284
        DoubleCheckLockSingleton result = singleton;

        if (result == null) {
            synchronized (DoubleCheckLockSingleton.class) {
                result = singleton;
                if (result == null) {
                    result = singleton = new DoubleCheckLockSingleton();
                }
            }
        }
        return result;
    }

}
4、饿汉式实现单例
package com.yrs.singleton;

/**
 * @Author: yangrusheng
 * @Description:
 * @Date: Created in 15:30 2018/7/2
 * @Modified By: 饿汉式单例,在类加载时会初始化静态变量,因此不会产生多个实例,也就不存在线程安全问题
 */
public class HungrySingleton {

    private static final HungrySingleton singleton = new HungrySingleton();

    private HungrySingleton() {
        // 防止通过反射的方式,调用构造方法实例化对象。
        if (singleton != null) {
            throw new IllegalStateException("Already initialized.");
        }
    }

    public static HungrySingleton getSingleton() {
        return singleton;
    }

    public static void doSomething() {

    }

}
5、饿汉式变形,静态代码块初始化
package com.yrs.singleton;

/**
 * @Author: yangrusheng
 * @Description: 饿汉式实现变形,静态代码块初始化
 * @Date: Created in 8:14 2018/7/17
 * @Modified By:
 */
public class StaticHungrySingleton {
    private static StaticHungrySingleton singleton = null;

    static {
        singleton = new StaticHungrySingleton();
    }

    private StaticHungrySingleton() {
        // 防止通过反射的方式,调用构造方法实例化对象。
        if (singleton != null) {
            throw new IllegalStateException("Already initialized.");
        }
    }

    public static StaticHungrySingleton getSingleton() {
        return singleton;
    }

    public static void doSomething() {

    }
}
6、静态内部类实现
package com.yrs.singleton;

/**
 * @Author: yangrusheng
 * @Description: 静态内部类实现单例模式。懒加载,jvm保证线程安全。
 * @Date: Created in 10:01 2018/7/6
 * @Modified By:
 */
public class StaticInnerClassSingleton {

    private StaticInnerClassSingleton() {
        // 防止通过反射的方式,调用构造方法实例化对象。
        if (SingletonHolder.INSTANCE != null) {
            throw new IllegalStateException("Already initialized.");
        }
    }

    public static StaticInnerClassSingleton getSingleton() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
    }

}
7、枚举类实现
package com.yrs.singleton;

/**
 * @Author: yangrusheng
 * @Description: 使用枚举来实现单例模式。线程安全,能够防止通过序列化或者反射的方式多次实例化。
 *                单元素的枚举类型已经成为实现Singleton的最佳方法。
 * @Date: Created in 10:59 2018/7/6
 * @Modified By:
 */
public enum  EnumSingleton {

    SINGLETON;

    @Override
    public String toString() {
        return getDeclaringClass().getCanonicalName() + "@" + hashCode();
    }

    public static void main(String[] args) {
        System.out.println(EnumSingleton.SINGLETON.toString());
    }
}
8、防止通过序列化或者反射的方式破坏单例特性
package com.yrs.singleton;

import java.io.*;

/**
 * @Author: yangrusheng
 * @Description: 当单例类变为可序列化(Serializable)的, 为了维护并保证Singleton,必须声明所有实例域都是transient的,
 *                并提供一个readResolve方法。
 * @Date: Created in 14:38 2018/7/6
 * @Modified By:
 */
public class SerializeHungrySingleton implements Serializable {

    // 为了保证Singleton,必须声明所有实例域都是transient的。
    private static final transient SerializeHungrySingleton singleton = new SerializeHungrySingleton();

    //私有构造方法确保不被通过new实例化出对象
    private SerializeHungrySingleton() {
        // 防止通过反射的方式,调用构造方法实例化对象。
        if (singleton != null) {
            throw new IllegalStateException("Already initialized.");
        }
    }

    public static SerializeHungrySingleton getSingleton() {
        return singleton;
    }

    // readResolve方法是为了保护单例属性。
    // 反序列化的时候会判断如果实现了serializable 或者 externalizable接口的类中又包含readResolve()方法的话,
    // 会直接调用readResolve()方法来获取实例。
    private Object readResolve() {
        return singleton;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SerializeHungrySingleton singleton = SerializeHungrySingleton.getSingleton();
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(singleton);

        //将对象从流中取出来
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        SerializeHungrySingleton singleton1 =  (SerializeHungrySingleton) ois.readObject();

        System.out.println(singleton == singleton1);
    }

}

优点:

* 内存中只有一个实例,减少内存开支
* 只生成一个实例,减少系统的性能开销
* 避免对资源的多重占用
* 优化和共享资源访问

缺点:

* 扩展困难,除非修改代码
* 不利于测试
* 与单一职责原则有冲突

代码地址:https://github.com/ByrsH/Design-Patterns/tree/master/Design%20Patterns/src/main/java/com/yrs/singleton

参考:

1.《设计模式之禅 第二版》
2. http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
3. https://github.com/iluwatar/java-design-patterns/tree/master/singleton/src/main/java/com/iluwatar/singleton

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值