01-单例模式

本文详细介绍了Java中的四种单例模式:懒汉式、饿汉式、静态内部类和枚举式,并分析了它们的优缺点以及在并发、反射和序列化情况下的表现。枚举式单例因其内在的线程安全性和对反射、序列化的防护成为最佳实践。同时,文章提供了多线程测试用例验证了单例模式在并发环境下的行为。

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

单例模式

前言

单例模式大家都很熟悉,基本上做开发的人都知道有这个模式,用途也很明确。但是深挖一下,就会发现好多东西。个人认为,单例模式是创建唯一对象并为其提供全局访问的方法。

单例模式有四种:懒汉式单例、饿汗式单例、静态内部类单例以及枚举式单例。这四种单例模式是一步步过渡的,最开始是懒汉式和饿汉式,两者一个是类使用时初始化、一个是类加载时直接初始化实例。然后就是静态内部类单例,利用静态内部类的特性使得外部类使用时不会加载内部类,调用到内部类的时候才会加载内部类,将懒汗式和饿汉式的优点结合。但是不论是懒汉式、饿汗式还是静态内部类都会被序列化后反序列化以及反射破坏单例,枚举式单例则是解决这一问题的答案。

文章中出现的代码用例可以到GitHub上查看

各类型单例实现

懒汉式单例

懒加载,对象使用时再去创建,私有构造方法并提供一个返回实例对象的方法,方法中对对象双重非空判断并加锁以及volatile关键字修饰对象禁止指令重排保证其线程安全。

实例
/**
 * 懒汉式单例:私有构造方法,双重非空校验,创建对象时加锁,volatile关键字禁止指令重排保证创建对象这一步骤的线程安全
 *
 * @author linmeng
 * @version 1.0
 * @date 2022年3月28日 16:42
 */
public class LazySingleton implements Serializable {
    private static volatile LazySingleton lazySingleton;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (Objects.isNull(lazySingleton)) {
            synchronized (LazySingleton.class) {
                if (Objects.isNull(lazySingleton)) {
                    lazySingleton = new LazySingleton();
                }
            }
        }

        return lazySingleton;
    }
}

优缺点

优点:类加载时不实例化对象,使用时才对对象进行实例化,减少内存消耗

缺点:使用了同步锁,对性能有一定影响,反射以及序列化后反序列化都能破坏单例

原理
volatile关键字作用

jvm创建对象分为三个步骤

  1. 为对象分配空间
  2. 初始化对象
  3. 将初始化的对象指向分配的空间

jvm在执行语句时,为了提高性能,有可能不按照代码顺序执行。这时候,代码执行顺序有可能变成1 3 2.这个时候一个线程走到3那一步,但是没有初始化对象,另一个线程判断非空,对象不为空,直接返回未初始化的对象进行使用,这就会造成空指针。

但是用了volatile关键字,禁止指令重排,就不会出现这种情况。

饿汉式单例

在程序加载时创建对象,调用时直接返回

实例
/**
 * 饿汉式单例:static关键字使得变量在类加载时就初始化,final关键字使得变量不可被修改
 * @author linmeng
 * @version 1.0
 * @date 2022年3月28日 16:55
 */
public class EagerSingleton implements Serializable {
    private static final EagerSingleton eagerSingleton = new EagerSingleton();

    private EagerSingleton(){}

    /**
     *
     * 直接返回
     * @author linmeng
     * @date 2021年11月9日 21:44
     * @return com.sword.www.designPattern.singleton.EagerSingleton
     */
    public static EagerSingleton getInstance(){

        return eagerSingleton;
    }
}
优缺点

优点:代码逻辑简单易懂

缺点:占用内存,反射、序列化后反序列化都能破坏单例

静态内部类单例

私有构造方法,定义一个静态内部类,内部类中声明对象,再定义一个获取实例的方法获取内部类中的实例。

实例
/**
 * 静态内部类单例:外部类加载时不会加载内部类,只有在使用到内部类的时候虚拟机才会加载内部类
 * 保证了线程安全,单例的唯一性,延迟了类的实例化
 * https://blog.csdn.net/mnb65482/article/details/80458571
 * https://juejin.cn/post/6871497501732519949
 *
 * @author linmeng
 * @version 1.0
 * @date 2022年3月28日 17:00
 */
public class StaticNestSingleton implements Serializable {

    private StaticNestSingleton() {
    }

    private static class SingletonHolder {
        private static final StaticNestSingleton SINGLETON = new StaticNestSingleton();
    }

    /**
     * 直接返回
     *
     * @return com.sword.www.designPattern.singleton.EagerSingleton
     * @author linmeng
     * @date 2021年11月9日 21:44
     */
    public static StaticNestSingleton getInstance() {

        return SingletonHolder.SINGLETON;
    }
}

优点:利用虚拟机的机制保证了线程安全。类加载时不会加载内部类,使用时才会加载,相当于懒汉式和饿汉式的优点结合

缺点:会被序列化后反序列化破坏单例

枚举式单例

直接定义一个枚举类。

实例
/**
 * https://www.cnblogs.com/happy4java/p/11206105.html
 * https://blog.csdn.net/weixin_48358336/article/details/120499442
 *
 * @author linmeng
 * @date 2022年3月28日 17:09
 * @return
 */
public enum EnumSingleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("do something");
    }
}
优缺点

优点:代码简单,保证了在多线程下的安全性。利用虚拟机机制防止反射、序列化后反序列化

缺点:哪有什么缺点

单例破坏

使用一些特殊手段可以同时实例化多个单例对象,这样就破坏了单例模式。破坏单例的手段有这几种:

  • 多线程:多线程下看获取到的单例对象是否为同一个,这个文中的单例代码均不受其破坏
  • 反射:反射使用私有构造除枚举式单例可以不被破坏,其他单例均需要在代码层面处理
  • 序列化后反序列化:将对象序列化成文件,再对其反序列化
多线程破坏

使用CountDownLatch使得多个线程同时获取单例对象,通过检查对象地址判断是否创建了多个单例对象从而破坏单例

测试用例
public void concurrentDestroy() throws InterruptedException, ExecutionException {
        // 定长线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        // 使得多个线程同一时刻启动
        CountDownLatch countDownLatch = new CountDownLatch(5);
        // 1:饿汉式
        System.out.println("饿汉式地址打印");
        List<Future<EagerSingleton>> eagerSingletonFutureList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            Future<EagerSingleton> future = executorService.submit(() -> {
                countDownLatch.countDown();
                return EagerSingleton.getInstance();
            });
            eagerSingletonFutureList.add(future);
        }
        countDownLatch.await();
        // 对象地址打印
        print(eagerSingletonFutureList);
        // 2:懒汉式
        System.out.println("懒汉式地址打印");
        List<Future<LazySingleton>> lazySingletonFutureList = new ArrayList<>();
        CountDownLatch countDownLatch1 = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            Future<LazySingleton> future = executorService.submit(() -> {
                countDownLatch1.countDown();
                return LazySingleton.getInstance();
            });
            lazySingletonFutureList.add(future);
        }
        countDownLatch1.await();
        // 对象地址打印
        print(lazySingletonFutureList);
        // 3:内部类
        System.out.println("内部类地址打印");
        List<Future<StaticNestSingleton>> staticNestSingletonFutureList = new ArrayList<>();
        CountDownLatch countDownLatch2 = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            Future<StaticNestSingleton> future = executorService.submit(() -> {
                countDownLatch2.countDown();
                return StaticNestSingleton.getInstance();
            });
            staticNestSingletonFutureList.add(future);
        }
        countDownLatch2.await();
        // 对象地址打印
        print(staticNestSingletonFutureList);
        // 4:枚举
        System.out.println("枚举相等判断");
        List<Future<EnumSingleton>> enumSingletonFutureList = new ArrayList<>();
        CountDownLatch countDownLatch3 = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            Future<EnumSingleton> future = executorService.submit(() -> {
                countDownLatch3.countDown();
                return EnumSingleton.INSTANCE;
            });
            enumSingletonFutureList.add(future);
        }
        countDownLatch3.await();
        // 对象地址判断
        System.out.println((enumSingletonFutureList.get(0).get()==enumSingletonFutureList.get(1).get())&&
                (enumSingletonFutureList.get(0).get()==enumSingletonFutureList.get(1).get()) &&
                (enumSingletonFutureList.get(1).get()==enumSingletonFutureList.get(2).get()) &&
                (enumSingletonFutureList.get(2).get()==enumSingletonFutureList.get(3).get()) &&
                (enumSingletonFutureList.get(3).get()==enumSingletonFutureList.get(4).get()));
    }
 /**
     * 打印对象
     * @param list
     * @param <T>
     */
    <T> void print(List<Future<T>> list){
        list.forEach(s ->{
            try {
                System.out.println(s.get().toString());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
打印结果
饿汉式地址打印
com.hello.designPattern.SingletonPattern.EagerSingleton@1e81f4dc
com.hello.designPattern.SingletonPattern.EagerSingleton@1e81f4dc
com.hello.designPattern.SingletonPattern.EagerSingleton@1e81f4dc
com.hello.designPattern.SingletonPattern.EagerSingleton@1e81f4dc
com.hello.designPattern.SingletonPattern.EagerSingleton@1e81f4dc
懒汉式地址打印
com.hello.designPattern.SingletonPattern.LazySingleton@48cf768c
com.hello.designPattern.SingletonPattern.LazySingleton@48cf768c
com.hello.designPattern.SingletonPattern.LazySingleton@48cf768c
com.hello.designPattern.SingletonPattern.LazySingleton@48cf768c
com.hello.designPattern.SingletonPattern.LazySingleton@48cf768c
内部类地址打印
com.hello.designPattern.SingletonPattern.StaticNestSingleton@4aa8f0b4
com.hello.designPattern.SingletonPattern.StaticNestSingleton@4aa8f0b4
com.hello.designPattern.SingletonPattern.StaticNestSingleton@4aa8f0b4
com.hello.designPattern.SingletonPattern.StaticNestSingleton@4aa8f0b4
com.hello.designPattern.SingletonPattern.StaticNestSingleton@4aa8f0b4
枚举相等判断
true

从结果中可以看到,并发情况下,上面各个类型的单例都可以返回相同的对象实例。总而言之,并发对文中编写的单例示例无破坏。接下来我们来看下反射对单例是否有破坏

反射破坏
测试用例
 /**
     * 反射单例破坏
     * https://blog.csdn.net/liuyingming910/article/details/42457265
     * @author linmeng
     * @date 2022年3月29日 11:13
     * @return void
     */
    @Test
    public void reflectDestroy() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        System.out.println("饿汉式单例反射测试");
        EagerSingleton eagerSingleton1 = EagerSingleton.getInstance();
        Constructor<EagerSingleton> declaredConstructor = EagerSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        EagerSingleton eagerSingleton = declaredConstructor.newInstance();
        System.out.println(eagerSingleton);
        System.out.println(eagerSingleton1);
        System.out.println(eagerSingleton == eagerSingleton1);
        System.out.println("懒汉式单例反射测试");
        LazySingleton lazySingleton1 = LazySingleton.getInstance();
        Constructor<LazySingleton> lazySingletonConstructor = LazySingleton.class.getDeclaredConstructor();
        lazySingletonConstructor.setAccessible(true);
        LazySingleton lazySingleton = lazySingletonConstructor.newInstance();
        System.out.println(lazySingleton1);
        System.out.println(lazySingleton);
        System.out.println(lazySingleton1 == lazySingleton);
        System.out.println("内部类单例测试");
        Class<StaticNestSingleton> staticNestSingletonClass = StaticNestSingleton.class;
        Constructor<StaticNestSingleton> staticNestSingletonConstructor = staticNestSingletonClass.getDeclaredConstructor();
        staticNestSingletonConstructor.setAccessible(true);
        StaticNestSingleton staticNestSingleton = staticNestSingletonConstructor.newInstance();
        StaticNestSingleton staticNestSingleton1 = StaticNestSingleton.getInstance();
        System.out.println(staticNestSingleton);
        System.out.println(staticNestSingleton1);
        System.out.println(staticNestSingleton1 == staticNestSingleton);

       System.out.println("枚举单例测试");
        EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
        Constructor<?>[] declaredConstructors = EnumSingleton.class.getDeclaredConstructors();
        for (Constructor<?> constructor : declaredConstructors) {
            System.out.println(constructor);
        }
        Constructor<?> enumSingletonConstructor = declaredConstructors[0];

//        System.out.println(enumSingletonConstructor);
        enumSingletonConstructor.setAccessible(true);
        Object enumSingleton1 = enumSingletonConstructor.newInstance("INSTANCE",0);
        System.out.println(enumSingleton == enumSingleton1);
    }
打印结果
饿汉式单例反射测试
com.hello.designPattern.SingletonPattern.EagerSingleton@78e03bb5
com.hello.designPattern.SingletonPattern.EagerSingleton@5e8c92f4
false
懒汉式单例反射测试
com.hello.designPattern.SingletonPattern.LazySingleton@61e4705b
com.hello.designPattern.SingletonPattern.LazySingleton@50134894
false
内部类单例测试
com.hello.designPattern.SingletonPattern.StaticNestSingleton@2957fcb0
com.hello.designPattern.SingletonPattern.StaticNestSingleton@1376c05c
false
枚举单例测试

枚举单例测试
private com.hello.designPattern.SingletonPattern.EnumSingleton(java.lang.String,int)

java.lang.IllegalArgumentException: Cannot reflectively create enum objects

	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.hello.designPattern.singletonPattern.SingletonTest.reflectDestroy(SingletonTest.java:146)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

不管是饿汉式、懒汉式还是静态内部类,反射都能够破坏其单例对象的唯一性,只有枚举式解决了反射破坏。

序列化破坏

这里我使用了common-lang3提供的序列化工具类SerializationUtils,依赖如下:

<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
测试用例
 @Test
    public void serializeDestroy(){
        System.out.println("饿汉式模式");
        EagerSingleton eagerSingleton = EagerSingleton.getInstance();
        EagerSingleton deserializeEagerSingleton = serializeAndDeserialize(eagerSingleton);
        System.out.println(eagerSingleton);
        System.out.println(deserializeEagerSingleton);
        System.out.println("懒汉式模式");
        LazySingleton lazySingleton = LazySingleton.getInstance();
        LazySingleton deserializeLazySingleton = serializeAndDeserialize(lazySingleton);
        System.out.println(lazySingleton);
        System.out.println(deserializeLazySingleton);
        System.out.println("内部类模式");
        StaticNestSingleton staticNestSingleton = StaticNestSingleton.getInstance();
        StaticNestSingleton serializeAndDeserializeStaticNestSingleton = serializeAndDeserialize(staticNestSingleton);
        System.out.println(staticNestSingleton);
        System.out.println(serializeAndDeserializeStaticNestSingleton);
        System.out.println("枚举式单例");
        EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
        EnumSingleton serializeAndDeserializeEnumSingleton = serializeAndDeserialize(enumSingleton);
        System.out.println(enumSingleton == serializeAndDeserializeEnumSingleton);
    }

    private <T extends Serializable> T serializeAndDeserialize(T singleton) {
        byte[] eagerSingletonSerializeBytes = SerializationUtils.serialize(singleton);
        return SerializationUtils.deserialize(eagerSingletonSerializeBytes);
    }
打印结果
饿汉式模式
com.hello.designPattern.SingletonPattern.EagerSingleton@65ae6ba4
com.hello.designPattern.SingletonPattern.EagerSingleton@3b192d32
懒汉式模式
com.hello.designPattern.SingletonPattern.LazySingleton@ed17bee
com.hello.designPattern.SingletonPattern.LazySingleton@2a33fae0
内部类模式
com.hello.designPattern.SingletonPattern.StaticNestSingleton@21588809
com.hello.designPattern.SingletonPattern.StaticNestSingleton@2aae9190
枚举式单例
true

同反射破坏一样,也是只有枚举式单例不被破坏

枚举类单例安全原因

在代码层面上,我没有对枚举式单例做任何处理,那他不被破坏的原因肯定是jdk的内部机制。

并发安全原因

对枚举式单例class文件进行反编译,反编译后代码如下

//  javap -c EnumSingleton.class 
public final class com.hello.designPattern.SingletonPattern.EnumSingleton extends java.lang.Enum<com.hello.designPattern.SingletonPattern.EnumSingleton> {
  public static final com.hello.designPattern.SingletonPattern.EnumSingleton INSTANCE;

  public static com.hello.designPattern.SingletonPattern.EnumSingleton[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[Lcom/hello/designPattern/SingletonPattern/EnumSingleton;
       3: invokevirtual #2                  // Method "[Lcom/hello/designPattern/SingletonPattern/EnumSingleton;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[Lcom/hello/designPattern/SingletonPattern/EnumSingleton;"
       9: areturn

  public static com.hello.designPattern.SingletonPattern.EnumSingleton valueOf(java.lang.String);
    Code:
       0: ldc           #4                  // class com/hello/designPattern/SingletonPattern/EnumSingleton
       2: aload_0
       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #4                  // class com/hello/designPattern/SingletonPattern/EnumSingleton
       9: areturn

  public void doSomething();
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #8                  // String do something
       5: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  static {};
    Code:
       0: new           #4                  // class com/hello/designPattern/SingletonPattern/EnumSingleton
       3: dup
       4: ldc           #10                 // String INSTANCE
       6: iconst_0
       7: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #12                 // Field INSTANCE:Lcom/hello/designPattern/SingletonPattern/EnumSingleton;
      13: iconst_1
      14: anewarray     #4                  // class com/hello/designPattern/SingletonPattern/EnumSingleton
      17: dup
      18: iconst_0
      19: getstatic     #12                 // Field INSTANCE:Lcom/hello/designPattern/SingletonPattern/EnumSingleton;
      22: aastore
      23: putstatic     #1                  // Field $VALUES:[Lcom/hello/designPattern/SingletonPattern/EnumSingleton;
      26: return
}

说实话,反编译后的代码,我也看不太懂,不过关键字还是可以看懂的,变量INSTANCE被static final修饰,static关键字使得其在类加载时就被初始化,final关键字使得其不可被修改,保证并发安全。

反射安全原因

反射获取对象时是使用的是enumSingletonConstructor.newInstance(“INSTANCE”,0)方法,我们可以看下方法源码,只截取判断的部分

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");

获取实例前会判断class对象是不是枚举,如果是枚举的话,直接抛异常。

序列化安全原因

枚举的反序列化使用的是Enum中的valueOf方法获取的对象,实际上还是用的反射获取所有的枚举对象,然后获取对应的枚举实例

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }
 Map<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
        // 反射调用values方法获取所有的美剧
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            // 将获取到的所有枚举以名称为key,枚举对象为value放到map中去
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }
    private volatile transient Map<String, T> enumConstantDirectory = null;

单例破坏问题解决

反射破坏问题解决

反射获取对象时,是直接调用其私有构造方法创建对象的,我们可以直接在构造方法中判断对象是否为空,如果不为空直接抛异常。静态内部类这个不知道怎么弄,有知道的小伙伴可以告诉一下,跪求😃。

		// 饿汉式
    private EagerSingleton() {
        if (eagerSingleton !=null){
            throw new RuntimeException();
        }
    }
    //懒汉式
     private LazySingleton() {
        if (lazySingleton != null) {
            throw new RuntimeException();
        }
    }

序列化破坏问题解决

反序列化的时候,会判断类中有没有readResolve方法,如果有的话,直接使用该方法获取返回对象,我们可以在类中自定义该方法。

public Object readResolve(){
        return eagerSingleton;
    }
public Object readResolve(){
        return lazySingleton;
    }
public Object readResolve(){
        return SingletonHolder.SINGLETON;
    }

建议

直接使用枚举式单例,无需任何额外代码处理,简单高效。

参考链接

命令行中 javac、java、javap 的使用详解

深度分析Java的枚举类型—-枚举的线程安全性及序列化问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值