第二章创建模式—单例设计模式
文章目录
- 单例模式的结构
- 如何控制只有一个对象呢
- 怎么设计这个类的内部对象
- 外部怎么访问
- 单例模式的主要有以下角色
- 单例模式的实现
- 饿汉式 1:静态变量
- 饿汉式 2:静态代码块
- 懒汉式 1:线程不安全
- 懒汉式 2:线程安全—方法级上锁
- 懒汉式 3:双重检查锁🚀
- 懒汉式 4:静态内部类🚀
- 饿汉式 3:枚举🚀
- 存在的问题
- 序列化破坏单例
- 反射破坏单例
- JDK 源码 - Runtime 类
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 所谓的单例模式保证某个类在程序中有且只有一个对象,类比现实生活中的地球类,只有一个地球对象
单例模式的结构
如何控制只有一个对象呢
- 我们创建一个对象是通过该类的构造方法进行创建对象
- 我们不能让外部进行创建对象,不然谁都能创建对象,那么就保证不了单例,那么我们的构造方法必须是private修饰,来控制对象的创建,也就是私有的构造方法
- 外部不能创建对象,那么这个创建唯一的对象的任务也就会在我们单例类来进行实现
怎么设计这个类的内部对象
- 首先我们知道在外部是创建不了对象的,所以这个对象的类型肯定不能是成员的,必须是静态的,通过类调用
- 因为成员变量的属性是通过我们的对象进行赋值,我们只有一个对象,如果是成员变量,就先要有对象才能赋值,而赋值了才有那个唯一的对象,就出现了死循环
外部怎么访问
- 一般属性我们用private进行修饰,为了实现其封装性
- 直接通过公开的方法获取这个唯一的对象
单例模式的主要有以下角色
- 单例类。只能创建一个实例的类
- 访问类。使用单例类
单例模式的实现
单例设计模式分类两种:
- 饿汉式:类加载就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式 1:静态变量
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton=new Singleton();//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton() {return singleton;}
}
- 因为singleton是类成员变量,在我 Singleton类加载过程中的初始化过程中进行创建对象赋值,因为只会执行一次,所以是天然的线程安全
- 缺点:singleton 对象是随着类的加载而创建的,如果该对象很大,却一直没有使用就会造成内存的浪费
饿汉式 2:静态代码块
该方式在成员位置声明 Singleton 类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;static {singleton=new Singleton();}//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton() {return singleton;}
}
该方式和饿汉式的方式 1 基本一样,所以该方式也存在内存浪费问题。
懒汉式 1:线程不安全
该方式在成员位置声明 Singleton 类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?
当调用 getInstance() 方法获取 Singleton 类的对象的时候才创建 Singleton 类的对象,这样就实现了懒加载效果。
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){if(singleton==null){singleton=new Singleton();}return singleton;}
}
测试
public class Client {public static void main(String[] args) {Thread thread1=new Thread(()->{System.out.println(Singleton.getSingleton());});thread1.start();System.out.println(Singleton.getSingleton());}
}
//com.lsc.itheima.pattern.singleton.demo3.Singleton@3d075dc0
//com.lsc.itheima.pattern.singleton.demo3.Singleton@6b047ec
- 发现不是同一个对象,没有实现其单例的效果
- 为什么不是线程安全的
- 比如我们的thread1线程调用了getSingleton,进行if(singleton==null)判断,进入了判断体
- 然后我们的main线程抢占了CPU,进行运行,进行if(singleton==null)判断,因为thread1线程并没有进行创建对象,也进入了判断体
- 故两个线程各自创建了对应的对象
懒汉式 2:线程安全—方法级上锁
在懒汉式 1 的基础上,使用 synchronized 关键字对getSingleton这个方法进行加锁
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static synchronized Singleton getSingleton(){if(singleton==null){singleton=new Singleton();}return singleton;}
}
- 但是该方法的执行效率特别低。
- 懒汉模式中加锁的问题,对于 getSingleton 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
- 注意:其实只有在初始化singleton的时候才会出现线程安全问题,一旦初始化完成就不存在了。
懒汉式 3:双重检查锁🚀
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){//第一次判断,如果singleton不为null,不进入抢锁阶段,直接返回实例if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
- 要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。
- 使用volatile关键字保证单例对象初始化不会被中断,保证其他线程获得的对象一定是初始化完成的对象
- 比如现在t1线程执行到了初始化的实例对象,正在执行new操作,还没完全结束(但是LazySingleTon!=null),然后t2线程执行到了判断唯一的对象是否为空,对于t2发现不为空,就直接返回了,但是返回的对象是没有完全初始化的,所以需要加volatile,就相当加了一层内存屏障,保证其他线程返回的对象必须等操作完全结束才能执行return语句
public class Singleton {//1定义静态成员变量,在类加载时创建// 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
懒汉式 4:静态内部类🚀
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。
public class Singleton {//2私有化构造方法private Singleton(){}// 定义一个静态内部类private static class SingletonHolder {// 在内部类中声明并初始化外部类的对象private static final Singleton singleton = new Singleton();}// 提供公共的访问方式public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
第一次加载 Singleton 类时不会去初始化 singleton,只有第一次调用 getInstance(),虚拟机加载 SingletonHolder 类并初始化 singleton,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
饿汉式 3:枚举🚀
首先,枚举方式是饿汉式单例模式,如果不考虑浪费内存空间的问题,这是极力推荐的单例实现模式。
因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。
枚举的写法非常简单,而且枚举方式是所用单例实现中唯一一种不会被破坏的单例实现模式。
public class SingleTon {/*** 饿汉式:枚举实现*/public enum Singleton {INSTANCE}
}
存在的问题
有两种方式可以使上面定义的单例类可以创建多个对象(枚举方式除外),分别是序列化和反射。
枚举方式是利用了 Java 特性实现的单例模式,不会被破坏,其他实现方式都有可能会被破坏
序列化破坏单例
问题演示:下面代码运行结果是false,表明序列化和反序列化破坏了单例设计模式。
public class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {writeObject2File();Singleton s1 = readObjectFormFile();Singleton s2 = readObjectFormFile();
// System.out.println(s1 == s2); // false}//从文件读取数据public static Singleton readObjectFormFile() throws IOException, ClassNotFoundException {//创建对象输入流对象ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\a.txt"));//2.读取对象Singleton singleton =(Singleton) objectInputStream.readObject();//3释放资源objectInputStream.close();return singleton;}//向文件写数据public static void writeObject2File() throws IOException {//1获取Singleton对象Singleton singleton = Singleton.getSingleton();//创建对象输出流对象ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));// 3.写对象objectOutputStream.writeObject(singleton);// 4.释放资源objectOutputStream.close();}
}
解决方案:在 Singleton 类中添加readResolve()方法。
在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象。
/*** 双重加锁方式(解决序列化破解单例模式)*/
public class Singleton implements Serializable {//1定义静态成员变量,在类加载时创建 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}/*** 下面是为了解决序列化反序列化破解单例模式* 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回*/private Object readResolve() {return getSingleton();}}
反射破坏单例
public class Client {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {//1 获取Singleton的字节码对象Class<Singleton> singletonClass = Singleton.class;//2获取无参构造方法对象Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();//3取消访问检查declaredConstructor.setAccessible(true);// 4.创建Singleton对象Singleton s1 = (Singleton) declaredConstructor.newInstance();Singleton s2 = (Singleton) declaredConstructor.newInstance();System.out.println(s1 == s2); // false}
}
解决方案:当通过反射方式调用构造方法进行创建时,直接抛异常
public class Singleton {//1定义静态成员变量,在类加载时创建 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;private static boolean flag = false;//2私有化构造方法// 私有构造方法private Singleton() {synchronized (Singleton.class) {// 如果是true,说明非第一次访问,直接抛一个异常,如果是false,说明第一次访问if (flag) {throw new RuntimeException("不能创建多个对象");}flag = true;}}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
JDK 源码 - Runtime 类
Runtime 类使用的就是单例设计模式。
源码查看:下面是 Runtime 类的源码,是静态变量方式的饿汉单例模式。
public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}...
}
/*** RuntimeDemo*/
public class RuntimeDemo {public static void main(String[] args) throws IOException {// 获取Runtime类的对象Runtime runtime = Runtime.getRuntime();// 调用runtime的方法exec,参数要的是一个命令Process process = runtime.exec("ifconfig");// 调用process对象的获取输入流的方法InputStream is = process.getInputStream();byte[] arr = new byte[1024 * 1024 * 100];// 读取数据int len = is.read(arr); // 返回读到的字节的个数// 将字节数组转换为字符串输出到控制台System.out.println(new String(arr, 0, len, "GBK"));}
}相关文章:
第二章创建模式—单例设计模式
文章目录 单例模式的结构如何控制只有一个对象呢怎么设计这个类的内部对象外部怎么访问 单例模式的主要有以下角色 单例模式的实现饿汉式 1:静态变量饿汉式 2:静态代码块懒汉式 1:线程不安全懒汉式 2:线程安全—方法级上锁懒汉式 …...
数据结构学习记录——堆的插入(堆的结构类型定义、最大堆的创建、堆的插入:堆的插入的三种情况、哨兵元素)
目录 堆的结构类型定义 最大堆的创建 堆的插入 堆的插入的三种情况 代码实现 哨兵元素 堆的结构类型定义 #define ElementType int typedef struct HNode* Heap; /* 堆的类型定义 */ struct HNode {ElementType* Data; /* 存储元素的数组 */int Size; /* 堆中…...
netperf测试
netperf测试 目录 批量网络流量性能测试 TCP_STREAM测试UDP_STREAM 测试请求/应答网络流量测试 TCP_RR TCP_CRR Netperf 是一个网络性能测试工具,它可以测试网络协议栈的性能,例如TCP和UDP协议。Netperf可以测量网络吞吐量、延迟和CPU利用率等指标。…...
ORACLE常用语句
1.修改用户密码 alter user 用户名 identified by 新密码; 2.表空间扩容 1.增加数据文件 alter tablespace AA add datafile ‘DATA’ size 20G autoextend off; 2.修改数据文件大小 ALTER DATABASE DATAFILE ‘E:\ORACLE\PRODUCT\10.2.0\ORADATA\aa\aa.DBF’ RESIZE 400M;…...
[论文笔记]C^3F,MCNN:图片人群计数模型
(万能代码)CommissarMa/Crowd_counting_from_scratch 代码:https://github.com/CommissarMa/Crowd_counting_from_scratch (万能代码)C^3 Framework开源人群计数框架 科普中文博文:https://zhuanlan.zhihu.com/p/65650998 框架网址:https…...
HCIP-7.2VLAN间通信单臂、多臂、三层交换方式学习
VLAN间通信单臂、多臂、三层交换方式学习 1、单臂路由2、多臂路由3、三层交换机的SVI接口实现VLAN间通讯3.1、VLANIF虚拟接口3.2、VLAN间路由3.2.1、单台三层路由VLAN间通信,在一台三层交换机内部VLAN之间直连。3.2.2、两台三层交换机的之间的VLAN通信。3.2.3、将物…...
PHP快速入门17-用spl_autoload_register实现类的自动加载
文章目录 前言实现过程创建两个类创建入口文件 总结 前言 本文已收录于PHP全栈系列专栏:PHP快速入门与实战 PHP类自动载入是指在PHP应用程序中,当需要使用某个类文件时,系统会自动加载该类文件,无需手动引入。 在PHP中…...
【黑马程序员 C++教程从0到1入门编程】【笔记8】 泛型编程——模板
https://www.bilibili.com/video/BV1et411b73Z?p167 C泛型编程是一种编程范式,它的核心思想是编写通用的代码,使得代码可以适用于多种不同的数据类型。 而模板是C中实现泛型编程的一种机制,它允许我们编写通用的代码模板,然后在需…...
分享10个精美可视化模板,解决95%的大屏需求!
前段时间和朋友一起喝茶,我吐槽着excel表格做报表的繁琐,他惊讶的问我竟然不知道大屏模板这种东西,说是直接套用数据就可以,我震惊的同时吃下了这个安利。 回来之后,我好好研究了一番这个叫可视化大屏的“新鲜玩意儿”…...
好用的项目管理软件的具体功能有哪些
随着企业规模不断的扩大,项目管理往往会面临更多的挑战与难题,最常见的会出现以下几个问题:资源消耗失控,而项目部门和相关部门之间沟通越来越困难;团队凝聚力下降、项目进度难以把控,项目成本几乎失控&…...
< 每日小技巧: 基于Vue状态的过渡动画 - Transition 和 TransitionGroup>
》基于Vue状态的过渡动画 - Transition 和 TransitionGroup 👉 一、Vue Transition 简介> Transition 和 TransitionGroup 之间的区别 👉 二、<Transition> 组件> 触发 <Transition> 组件的场景:> 基于 CSS 的过渡效果&…...
vmware安装redhat 8
vmware安装redhat 8 1、下载镜像文件1.1 镜像文件 2、安装系统2.1、选择自定义安装2.2、兼容性选择2.3、选择镜像文件导入2.4、设置用户名密码2.5、选择虚拟机在磁盘上的位置2.6、选择处理器数量2.7、选择内存大小2.8、选择桥接或NAT2.9、选择SCSI控制器类型2.10、选择虚拟机磁…...
OpenCV C++案例实战三十一《动态时钟》
OpenCV C案例实战三十一《动态时钟》 前言一、绘制表盘二、绘制刻线三、获取系统时间四、结果展示五、源码总结 前言 本案例将使用OpenCV C实现动态时钟效果。原理也很简单,主要分为绘制表盘、以及获取系统时间两步。 一、绘制表盘 首先为了效果显示美观一点&…...
字节后端入门 - Go 语言原理与实践
1.1什么是Go语言 1.2Go语言入门 环境 1.3基础语法 1.3.1变量 var name"value" 自己推断变量类型; 也可以显式类型 var c int 1 name: type(value) 常量: const name "value" g : a"foo" 字符串拼接 1.3.2 if else {}花括号…...
锂电材料浆料匀浆搅拌设备轴承经常故障如何处理?
锂电材料浆料匀浆搅拌设备是锂电池生产中重要的设备之一,用于将活性材料、导电剂、粘结剂和溶剂混合成均匀的浆料,是电极制备过程中不可或缺的步骤。然而,由于高速搅拌和化学腐蚀等因素的影响,轴承经常会出现故障,导致…...
设计模式——设计模式介绍和单例设计模式
导航: 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 一、设计模式概述和分类 1.1 设计模式介绍 1.2 设计模式分类 二、创建型设计模式-单例模式 2.1 介绍 2.2 八种单例模式的创…...
利用Iptables构建虚拟路由器
利用Iptables构建虚拟路由器 (1)修改网络类型 在VMware Workstation软件中选择“编辑→虚拟网络编辑器”菜单命令,在虚拟网络列表中选中VMnet1,将其配置为“仅主机模式(在专用网络内连接虚拟机)”&#x…...
C++——类和对象[中]
0.关注博主有更多知识 C知识合集 目录 1.类的默认成员函数 2.构造函数和析构函数基础 3.构造函数进阶 4.析构函数进阶 5.拷贝构造函数 6.运算符重载 7.日期类 7.1输入&输出&友元函数 8.赋值运算符重载 9.const成员函数 9.1日期类完整代码 10.取地址重载 …...
Symbol.iterator和Symbol.asyncIterator
Symbol是什么? symbol是ES6标准中新增的一种基本数据类型,symbol 的值是通过 Symbol()函数返回的,每一个 symbol 的值都是唯一的,即使传入相同的描述值。 注:Symbol 函数不允许通过 new 的方式调用 Symbol的作用是什…...
忆暖行动|“他一个人推着老式自行车在厚雪堆的道路上走,车上带着学生考试要用的司机”
忆暖行动|“他一个人推着老式自行车在厚雪堆的道路上走,车上带着学生考试要用的sj” 一头白发,满山青葱 在那斑驳的物件褶皱中,透过泛黄的相片,掩藏着岁月的冲刷和青葱的时光。曾经的青年早已经不复年轻,但是那份热爱…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
