单例模式(史上最全)

news2024/10/5 9:39:49

文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :

免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《尼恩Java面试宝典 最新版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取


该如何优雅的、安全的使用单例模式呢?

单例模式是Java的核心模式,最好人人都精通。

那么,该如何优雅的使用单例模式呢?

来看看:

  • 缓存之王 Caffeine 源码中,如何使用单例模式的?
  • 链路之王 Skywalking 源码中,如何使用单例模式的?

另外,也看看 美团是如何进行 单例模式 的面试的。下面是一个美团面试题:

  • 单例模式懒汉式和饿汉式有哪些区别?(美团)

注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从下面的链接获取:语雀 或者 码云

1.什么是单例

  • 保证一个类只有一个实例,并且提供一个访问该全局访问点

2.那些地方用到了单例模式

  1. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
  2. 应用程序的日志应用,一般都是单例模式实现,只有一个实例去操作才好,否则内容不好追加显示。
  3. 多线程的线程池的设计一般也是采用单例模式,因为线程池要方便对池中的线程进行控制
  4. Windows的(任务管理器)就是很典型的单例模式,他不能打开俩个
  5. windows的(回收站)也是典型的单例应用。在整个系统运行过程中,回收站只维护一个实例。

3.单例优缺点

优点:

  1. 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例
  2. 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
  3. 提供了对唯一实例的受控访问。
  4. 由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
  5. 允许可变数目的实例。
  6. 避免对共享资源的多重占用。

缺点:

  1. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
  2. 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
  3. 单例类的职责过重,在一定程度上违背了“单一职责原则”。
  4. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

4.单例模式使用注意事项:

  1. 使用时不能用反射模式创建单例,否则会实例化一个新的对象
  2. 使用懒单例模式时注意线程安全问题
  3. 饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)

5.单例防止反射漏洞攻击

private static boolean flag = false;

private Singleton() {

	if (flag == false) {
		flag = !flag;
	} else {
		throw new RuntimeException("单例模式被侵犯!");
	}
}

public static void main(String[] args) {

}

6.如何选择单例创建方式

  • 如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。
    如果需要延迟加载,可以使用静态内部类或者懒汉式,相对来说静态内部类好于懒韩式。
    最好使用饿汉式

7.单例创建方式

(主要使用懒汉和懒汉式)

1.饿汉式:
类初始化时,会立即加载该对象,线程天生安全,调用效率高。

2.懒汉式:
类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。

3.静态内部方式:
结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。

4.枚举单例:
使用枚举实现单例模式

优点: 实现简单、调用效率高,枚举本身就是单例, 由jvm从根本上提供保障!避免通过反射和反序列化的漏洞;

缺点: 没有延迟加载。

5.双重检测锁方式

因为JVM重排序、内存可见性的原因,可能会初始化多次,

所以: 需要通过 Double Check 双重检查+ synchronized + Volatile 解决 同步问题和可见性问题。

1.饿汉式

类初始化时,会立即加载该对象,线程天生安全,调用效率高。

package com.crazymakercircle.designmodel.singleton;
//饿汉式
public class FSingleton {

    // 类初始化时,会立即加载该对象,线程安全,调用效率高
    private static final FSingleton instance = new FSingleton();

    // 私有化构造方法
    private FSingleton() {
    }

 public   static FSingleton getInstance() {
        return instance;
    }


}


饿汉模式就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。

特点:

  • 是否 Lazy 初始化:否
  • 是否多线程安全:是
  • 实现难度:易

优点:

  • 没有加锁,执行效率会提高。
  • 这种方式比较常用,但容易产生垃圾对象
  • 它基于JVM class loader 机制, 是单线程执行的, 避免了多线程的同步问题

缺点:

  • 类加载时就初始化,浪费内存,

2.懒汉式

类初始化时,不会初始化该对象,

真正需要使用的时候,才会创建该对象,具备懒加载功能。

package com.crazymakercircle.designmodel.singleton;
//懒汉模式
public class FLazySingleton {

    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static  FLazySingleton instance = null;

    // 私有化构造方法
    private FLazySingleton() {
    }

    //真正需要使用的时候才会创建该对象
    public static synchronized FLazySingleton getInstance() {
        if(null==instance)
        {
            instance=new FLazySingleton();
        }
        return instance;
    }
}

3.静态内部类

静态内部方式:

结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。

package com.crazymakercircle.designmodel.singleton;

public class Singleton { 
    //静态内部类 
    private static class LazyHolder { 
          //通过final保障初始化时的线程安全  
           private static final Singleton INSTANCE = new Singleton(); 
    } 
       //私有的构造器 
    private Singleton (){} 
      //获取单例的方法 
    public static final Singleton getInstance() { 
      //返回内部类的静态、最终成员 
       return LazyHolder.INSTANCE; 
    } 
} 

4.枚举单例式

枚举单例:

使用枚举实现单例模式 优点:实现简单、调用效率高,

枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。

package com.lijie;

package com.crazymakercircle.designmodel.singleton;
//饿汉式
public enum SingletonEnumStyle {
    INSTANCE;
    // 类初始化时,会立即加载该对象,线程安全,调用效率高

    public  static SingletonEnumStyle getInstance() {
        return INSTANCE;
    }

}

枚举实现单例模式 优点:

  • 实现简单、枚举本身就是单例,由jvm从根本上提供保障!
  • 避免通过反射和反序列化的漏洞

缺点:

  • 没有延迟加载

5.双重检测锁方式

所谓懒加载,就是直到第一次被调用时才加载。其实现需要考虑并发问题和指令重排,代码如下:

public class Singleton {
 
    private volatile static Singleton instance; //①
 
    private Singleton() { //②
    }
 
    public static Singleton getInstance() {
        if (instance == null) {//③
            synchronized (Singleton.class) {
                if (instance == null) {//④
                    instance = new Singleton();//⑤
                }
            }
        }
        return instance;
    }
}

这段代码精简至极,没有一个字符是多余的,下面逐行解读一下:

首先,注意到①处的volatile关键字,它具备两项特性:

一是保证此变量对于所有线程的可见性。

即当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。

二是禁止指令重排序优化。

这里解释一下指令重排序优化:

代码 ⑤ 处的instance = new Singleton(); 并不是原子的,大体可分为如下 3 步:

  1. 分配内存
  2. 调用构造函数初始化成实例
  3. 让instance指向分配的内存空间

JVM 允许在保证结果正确的前提下进行指令重排序优化。

即如上 3 步可能的顺序为1->2->3 或 1->3->2 。

如果顺序是 1->3->2 ,当 3 执行完,2 还未执行时,另一个线程执行到代码 ③ 处,发现instance不为null,直接返回还未初始化好的instance并使用,就会报错。

所以使用volatile,就是为了保证线程间的可见性和防止指令重排。

其次,代码②处将构造函数声明为private目的:在于阻止使用new Singleton()这样的代码生成新实例。

最后,当客户端调用Singleton.getInstance()时,先检查是否已经实例化(代码③),未实例化时同步代码块,然后再次检查是否已实例化(代码④),然后才执行代码⑤。

两次检查的意义在于,防止synchronized同步过程中其他线程进行了实例化。

这就是著名的双重检查锁(Double check lock)实现单例,也即懒加载。

TIPS:

网上也有直接对 getInstance()方法加锁的版本,这样大范围的方法级别加锁会导致并发变低,实际上第一次调用生成实例之后,后续获取实例根本不需要并发控制了。

而本例的双重检查锁版本可以避免此并发问题。

双重检测锁 单例 非常重要, 涉及到Volatile 和可见性的底层原理, 深入学习/系统学习 双重检测锁 单例的内容, 请参见 《Java 高并发核心编程 卷2》 第8.1节:线程安全的单例模式

缓存之王 Caffeine 源码中,如何使用单例模式的?

答案是:枚举单例

并且,单例的名称叫做 INSTANCE

通过这个 INSTANCE 名字 做 关键词搜索, 能搜到一大把

来一个案例

再来一个案例

缓存之王 Caffeine 的详细资料,请参考下面的博客、或者对应的PDF文件:

  • 《彻底穿透 缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
  • 《彻底穿透 缓存之王:Caffeine 的使用(史上最全)》

链路之王 Skywalking 源码中,如何使用单例模式的?

答案是:枚举单例

并且,单例的名称叫做 INSTANCE

8 单例模式懒汉式和饿汉式有哪些区别?(美团)

单例模式是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

明确定义后,看一下代码:

饿汉模式

package com.crazymakercircle.designmodel.singleton;
//饿汉式
public class FSingleton {

    // 类初始化时,会立即加载该对象,线程安全,调用效率高
    private static final FSingleton instance = new FSingleton();

    // 私有化构造方法
    private FSingleton() {
    }

 public   static FSingleton getInstance() {
        return instance;
    }
}

饿汉模式就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。

特点:

  • 是否 Lazy 初始化:否
  • 是否多线程安全:是
  • 实现难度:易

优点:

  • 没有加锁,执行效率会提高。
  • 这种方式比较常用,但容易产生垃圾对象
  • 它基于JVM class loader 机制, 是单线程执行的, 避免了多线程的同步问题

缺点:

  • 类加载时就初始化,浪费内存,

懒汉模式

public class Singleton {
 
    private volatile static Singleton instance; //①
 
    private Singleton() { //②
    }
 
    public static Singleton getInstance() {
        if (instance == null) {//③
            synchronized (Singleton.class) {
                if (instance == null) {//④
                    instance = new Singleton();//⑤
                }
            }
        }
        return instance;
    }
}

而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

特点:

  • 是否 Lazy 初始化:是
  • 是否多线程安全:是
  • 实现难度:难

1、线程安全:

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,需要通过多种手段,保证线程安全和内存可见性:

  • volatile 保证内存可见性
  • synchronized + 双重检查 保证线程安全

2、资源加载和性能:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。

而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

  • 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要解决:一个全局使用的类频繁地创建与销毁。
  • 何时使用:当您想控制实例数目,节省系统资源的时候。
  • 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  • 关键代码:构造函数是私有的。
  • 应用实例:

1、一个党只能有一个主席。
2、Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。

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

使用场景:

1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用 Double Check 双重检查锁,synchronized (Singleton.class) 防止多线程同时进入造成instance 被多次实例化。

注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从下面的链接获取:语雀 或者 码云

推荐阅读:

  • 《尼恩Java面试宝典》

  • 《Springcloud gateway 底层原理、核心实战 (史上最全)》

  • 《Flux、Mono、Reactor 实战(史上最全)》

  • 《sentinel (史上最全)》

  • 《Nacos (史上最全)》

  • 《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》

  • 《clickhouse 超底层原理 + 高可用实操 (史上最全)》

  • 《redis 集群 实操 (史上最全、5w字长文)》

  • 《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》

  • 《红黑树( 图解 + 秒懂 + 史上最全)》

  • 《分布式事务 (秒懂)》

  • 《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》

  • 《缓存之王:Caffeine 的使用(史上最全)》

  • 《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》

  • 《Docker原理(图解+秒懂+史上最全)》

  • 《Redis分布式锁(图解 - 秒懂 - 史上最全)》

  • 《Zookeeper 分布式锁 - 图解 - 秒懂》

  • 《Zookeeper Curator 事件监听 - 10分钟看懂》

  • 《Netty 粘包 拆包 | 史上最全解读》

  • 《Netty 100万级高并发服务器配置》

  • 《Springcloud 高并发 配置 (一文全懂)》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/78321.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

0121 动态规划 Day10

剑指 Offer 46. 把数字翻译成字符串 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。…

Python——翻转字符串

题目介绍 以空格为分割,将字符串中的每个单词的字母位置不变,单词顺序从后往前翻转 例如:I am a student. 变成:student. a am I Python中的标准库是为了提高程序员开发效率,减少学习成本,而设计的一系列方…

spring——Spring Bean定义

在 XML 配置的<beans> 元素中可以包含多个属性或子元素&#xff0c;常用的属性或子元素如下表所示。 属性名称描述idBean 的唯一标识符&#xff0c;Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始&#xff0c;可以使用字母、数字、下划线等…

SpringCloud Gateway网关的使用与介绍

目录 1. gateway简介 1.1 是什么 1.2 作用 1.3 主要特征 1.4 与zuul的主要区别 1.5 主要组件 1.6 架构图 2. 开发示例 2.1 创建一个gateway模块 2.2 与nacos结合使用 2.2.1 默认规则 2.2.2 通过配置文件配置路由 2.2.3 动态路由 1. gateway简介 1.1 是什么 Spri…

Vulnhub靶机:PRIME_ 1

目录介绍信息收集主机信息探测主机信息探测网站探测目录爆破排雷dirsearch强制访问文件包含漏洞利用WordPress提权wordpress配置文件内核提权介绍 系列&#xff1a;Prime&#xff08;此系列共1台&#xff09; 发布日期&#xff1a;2019年9月1日 难度&#xff1a;初-中 运行环境…

在 Istio 服务网格中使用 Argo Rollouts 实现智能的渐进式发布

1 Argo Rollouts 介绍 Kubernetes 原生的 Deployment 利用 Rolling Update 滚动更新的策略在应用升级时提供基本的安全保证&#xff08;例如就绪探针&#xff09;。然而默认的滚动更新策略存在着一些明显的缺点&#xff0c;例如&#xff1a; 无法控制流向新版本的流量。无法控…

tensorflow入门(四)如何用tensorflow训练神经网络

参考 如何用tensorflow训练神经网络 - 云社区 - 腾讯云 在使用神经网络解决实际的分类或回归问题时需要设置好参数取值。下面介绍使用监督学习的方式来合理地设置参数取值&#xff0c;同时也将给出tensorflow程序来完成这个过程。设置神经网络参数的过程就是神经网络的训练过…

基于JDBC的MySQL数据库编程

✨博客主页: 荣 ✨系列专栏: MySQL ✨一句短话: 难在坚持,贵在坚持,成在坚持! 文章目录一. JDBC概述二. JDBC前置工作1. 准备好MySQL驱动包2. 创建项目三. JDBC的使用步骤1. 创建数据源DataSourece2. 连接数据库3. 构造并执行sql语句4. 释放资源5. sql语句不要写死(以插入为例)…

C++入门基础07:函数定义与声明、函数传参(传值、传地址、传引用)、函数重载

C入门基础07&#xff1a;函数定义与声明、函数传参&#xff08;传值、传地址、传引用&#xff09;、函数重载 1、函数定义与声明 函数是一起执行一个任务的一组语句。每个程序&#xff08;C/C&#xff09;都有一个主函数 main() &#xff0c; 所有简单的程序都可以定义其他额…

1563_AURIX_TC275_EVR的控制寄存器

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 1. 连续的写入必须确保是解锁状态&#xff0c;否则的话可能会导致所有的总线阻塞。 2. 多核系统中&#xff0c;尽量写入之后再读取一下确认写入的状态。 这里是过压以及欠压的几个监控结果…

if、for、while结构的用法

分支与循环的流程控制一、分支流程控制1. if分支语句结构1). 单分支2). 双分支3). 三元运算符&#xff08;双分支的简化&#xff09;4). 多分支二. 循环流程控制1. while循环语句语法结构1.while循环用法2.while 的死循环3. while else的用法2. for循环语句语法结构1. for和ran…

嵌入式开发学习之--串口通讯(上)

提示&#xff1a;本篇开始学习各种通讯方式&#xff0c;重中之重。 文章目录前言一、 串口通讯协议简介1.1 物理层1.2 协议层1.2.1 基本组成。1.2.2 波特率1.2.3 起始和终止位1.2.4 有效数据1.2.5 数据校验二、USART结构体详解总结前言 作为一个嵌入式的开发者&#xff0c;解析…

网页木马挂马的实现与防范

一、网页挂马是什么 不少用户都碰到过这样的现象&#xff1a;打开一个网站&#xff0c;结果页面还没显示&#xff0c;杀毒软件就开始报警&#xff0c;提示检测到木马病毒。有经验的朋友会知道这是网页恶意代码&#xff0c;这就是典型的网页挂马现象。那么是什么原因导致了这种…

数据库概论之MySQL表的增删改查 - 进阶版本1

MySQL表的增删改查 - 进阶1、数据库约束1.1 约束类型1.2 NULL约束1.3 UNIQUE约束1.4 DEFAULT约束1.5 PRIMARY约束1.6 FOREIGN KEY外键约束1.6.1 语法1.6.2 工作原理2、表的设计2.1 一对一2.2 一对多2.3 多对多大家好&#xff0c;已经好久没更新了 , 学校的学业有点忙 , 没有额外…

[论文解析] Denoising Diffusion Probabilistic Models

文章目录OverviewsWhat problem is addressed in the paper?What is the key to the solution?What is the main contribution?Contents扩散概率模型背景算法实验结论Overviews What problem is addressed in the paper? We present high quality image synthesis result…

【Java面试】说说类加载机制(流程)

文章目录加载流程装载(Load)链接(Link)验证(Verify)准备(Prepare)解析(Resolve)初始化(Initialize)卸载(Unload)加载流程 类的加载流程如下&#xff1a; 转载(Load)->链接(Link)->初始化(Initialize)->使用(Use)->卸载(Unload) 其中链接又包含验证(Verify)&#x…

rabbitMQ延时队列——TTL和DLX

一. 场景&#xff1a;“订单下单成功后&#xff0c;15分钟未支付自动取消” 1.传统处理超时订单 采取定时任务轮训数据库订单&#xff0c;并且批量处理。其弊端也是显而易见的&#xff1b;对服务器、数据库性会有很大的要求&#xff0c; 并且当处理大量订单起来会很力不从…

flask前后端项目--实例-前端部分:-4-vue-Element Plus

flask前后端项目--实例-前端部分&#xff1a;-4-vue-Element Plus组件添加事项 一、实验测试步骤 1.Element Plus添加 1.先备份App.VUE&#xff0c;然后修改app.vue的内容&#xff0c;数据来源资Element Plus的表格table 2. 数据来源资Element Plus的表格table 3. 运行服务&…

023_SSS_Neural 3D Video Synthesis from Multi-view Video(CVPR2022)

Neural 3D Video Synthesis from Multi-view Video(CVPR2022) 本文提出了一种新的3D视频生成方法&#xff0c;这种方法能够以紧凑但富有表现力的表示形式表示动态真实世界场景的多视图视频记录&#xff0c;从而实现高质量的视图合成和运动插值。 1. Introduction 本文的主要…

百度地图 ( 一 ) 显示地图

1.百度地图 百度地图开放平台 https://lbsyun.baidu.com/ 使用百度地图时导入JavaScript包 <script type"text/javascript" src"http://api.map.baidu.com/api?v2.0&ak您的密钥"></script>1.1.如何申请 ak 密钥 在 开发平台 找 控制…