【多线程】单例模式

🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈

在这里插入图片描述

文章目录

  • 1. 单例模式的初识
  • 2. 单例模式的含义
  • 3. 单例模式实现的两种方式
    • 3.1 饿汉模式
    • 3.2 懒汉模式
      • 3.2.1 懒汉模式(单线程版)
      • 3.2.2 懒汉模式(多线程版)
        • 1)多线程情况下为什么只讨论懒汉模式而不讨论饿汉模式呢?
        • 2)懒汉模式线程不安全的原因
        • 3)解决方式
          • a. 优化一:修改锁的位置解决效率问题
          • b. 优化二:使用volatile修饰解决 new 操作引发指令重排序
  • 4. 面试题 —— 单例模式的线程安全问题

1. 单例模式的初识

何为单例模式呢~
单例模式
单例模式是一种经典的设计模式,经常考的设计模式之一,所以是十分重要的

这里又有一个新的概念,设计模式是什么?
设计模式
相当于软件开发中的棋谱,一些大佬们针对一些常见的场景,总结出来的代码编写套路,按照套路来写,不能说代码可以编写得多么好,但是至少不会很差,这类似于在下棋中,通过前人总结出来的一些固定下棋套路,按照这些棋谱来下,不能说下得多么漂亮,但是至少不会很差,这两者是一个道理,这种设计模式相当于兜底,给我们了一种模板

其中,设计模式有很多种,之前有个大佬写了本书,流传很广,讨论23种设计模式,很多人就认为设计模式只有23种,并不是的,只是在这本书中讨论了这23种设计模式,其实设计模式还有很多种!!!

在我们这个阶段,主要考察两个设计模式:
1)单例模式
2)工厂模式

设计模式需要我们有一定的开发经验的积累,才好理解,尽管我们现在还没有积累一定的经验,我们还是可以尝试去理解它,在实际应用开发中,我们将会有一个更深刻的了解,下面我们一起来看看单例模式!

2. 单例模式的含义

从字面理解,单例就是单个实例(instance),即在一个程序中,某一个类,只能创建出一个实例(一个对象),不能创建多个对象! (回顾JavaSE中所学的知识,类的实例就是对象~)
这里不是说多 new 几次,就可以创建多个对象,在语法上有办法禁止多 new,在Java中的单例模式,借助Java语法,保证某个类只能创建出一个实例,而不能new多次,不能创建多个对象

单例模式能保证某个类在程序中只存在唯一一个实例,而不会创建出多个实例
场景需要】有些场景就需要某个概念是单例的,比如在生活中,一夫一妻制,再比如,JDBC中的 DataSource 实例就只需要一个

3. 单例模式实现的两种方式

在Java语法中,实现单例模式有很多种写法,本文主要介绍以下两种实现方式:
1)饿汉模式(急迫版)
2)懒汉模式(从容版)

3.1 饿汉模式

在生活中,吃完饭后去洗碗,饿汉模式 —— 吃完之后立刻去洗碗,超急迫的~
对应计算机中的栗子,打开一个硬盘上的文件,读取文件内容,并显示出来,饿汉模式 —— 把文件所有内容都读到内存中并显示出来
但是假设文件非常大,比如10G,饿汉模式文件打开可能都要半天,内存够不够我们都不清楚~

饿汉模式 —— 类加载的同时创建实例,代码如下:

// 把这个类设置为单例
class Singleton {private static Singleton instance = new Singleton();//获取到实例的方法public static Singleton getInstance() {return instance;}//禁止new 将构造方法设置为privateprivate Singleton() {};
}public class ThreadDemo14 {public static void main(String[] args) {Singleton s1 =  Singleton.getInstance();//此时s1和s2是同一个对象//Singleton s2 =  Singleton.getInstance();//此时不能再进行new了,外部无法创建实例//Singleton s3 = new Singleton();}
}

结果分析:
1)s1 和 s2 获取到的其实是同一个对象
2)运行 s3,将会报错,因为外部无法再 new 一个对象,已禁止该操作了
在这里插入图片描述
具体实现过程如下:
在这里插入图片描述
以上就是饿汉模式代码,通过Java语法来限制类实例的多次创建保证单例的特性:

  1. staic 修饰 instance,保存单例对象的唯一实例
  2. 并用 private 修饰 instance,将该实例进行封装
  3. 如果要获取该实例,通过调用 getInstance() 方法获取这个实例
  4. 将构造方法用 private 修饰,可禁止外部 new 实例操作,即不可多次 new 对象
    在这里插入图片描述

对于 private 修饰的方法,我们会有一个疑问:反射不是可以获取到私有方法吗?
1)反射本身就是一个非常规的手段,反射本身就是不安全的(能不用就不用)
2)单例模式有一种实现方式可以保证反射下得安全,通过枚举即使使用反射也可以保证单例(这里不作过多介绍)

但是饿汉模式存在一个问题,那就是实例的创建时机过早了,可以看到,实例在类内部就创建好了,只有类一加载,就会创建出这个实例,如果后面并没有用到这个实例,其实会有点浪费的意思,更好的实现方式是懒汉模式,即用的时候再创建,下面介绍懒汉模式

3.2 懒汉模式

在计算机中,谈到"懒",一般其实是褒义词,想想为什么我们的科技能够进步,社会能够发展,其本质动力,都是为了更便捷,源动力全靠"懒"~

继续洗碗的栗子,懒汉模式 —— 吃完饭后,先把碗放着,等到下一顿吃饭时,需要用到碗时再去洗,超从容!
继续打开硬盘文件的栗子,饿汉模式 —— 只把文件读一小部分,把当前屏幕填充上,如果用户翻页了,再读其它文件内容,如果不翻页,就不需要再去读
如果文件非常大,懒汉模式就可以快速打开,毕竟不用一次都打开完,等需要某部分就打开某部分
(尽管懒汉模式会增加硬盘的读取次数,但是和饿汉模式情况相比,其实是不值得一提的~)

通常认为,懒汉模式更好,效率更高,核心思想:非必要,不创建,即非必要不去做某事,等到要去做某事的时候再去做

3.2.1 懒汉模式(单线程版)

懒汉模式(单线程版) —— 类加载的时候不创建实例,第一次使用的时候才创建实例,即需要使用这个实例的时候才创建它 ,代码如下:

//懒汉模式实现单例模式
//懒汉模式实现单例模式
class SingletonLazy {private static SingletonLazy instance = null; //先置为空public static SingletonLazy getInstance() { //只有调用这个才会创建对象if (instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy() { }
}public class ThreadDemo15 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}

运行结果如下:s1 和 s2 获取到的是同一个对象,所以结果返回 true

在这里插入图片描述

具体实现过程如下:
在这里插入图片描述

  1. 先将 instance 设置为 null
  2. 当需要使用 instance 的时候,调用getInstance()方法,如果 instance 为null,则需 new 一个,不为空,则说明已经有一个实例,不需要new,直接使用该实例(单例模式就是一个类只有一个实例)
  3. 使用单例,调用getInstance()方法

以上就是懒汉模式代码,与饿汉模式代码的实现方式类似,最大的区别在于懒汉模式只有在需要使用实例时才会创建,所以要将创建实例写在getInstance()方法里面,懒汉模式通过Java语法来限制类实例的多次创建保证单例的特性与饿汉模式一致,这里就不再赘述啦~

3.2.2 懒汉模式(多线程版)

1)多线程情况下为什么只讨论懒汉模式而不讨论饿汉模式呢?

上述的两个代码,是否线程安全呢?即多个线程下调用 getInstance()方法,是否会出现问题?
在这里插入图片描述
结论
饿汉模式天然线程就是安全的,因为只是读数据
懒汉模式是线程不安全的,因为有读有写

所以,为什么讨论多线程下懒汉模式,是因为懒汉模式在多线程下,可能无法保证创建对象的唯一性,会出现问题,我们需要一定的措施去解决这个问题以保证它是线程安全的,而饿汉模式本身则是线程安全的~

2)懒汉模式线程不安全的原因

回顾线程不安全的原因:线程不安全原因

  1. 抢占式执行
  2. 修改共享数据
  3. 修改操作不是原子的
  4. 内存可见性
  5. 代码顺序性(指令重排序)

懒汉模式线程不安全的最直接原因 —— 多个线程修改同一个变量
在这里插入图片描述
分析
在饿汉模式中,getInstance()方法直接进行返回,没有涉及到改的操作
而在懒汉模式中,getInstance()方法需要先判断 instance 是否为 null,如果是的,就需要对 instance 进行修改, new 一个实例,再返回,如果不是则直接返回

通过上述分析,可以知道在懒汉模式中涉及到修改的操作,在多线程下,由于有多个线程,可能会创建出多个实例,无法保证创建对象的唯一性!下面进行进一步分析:
在这里插入图片描述

严重性
如果是N个线程一起调用 getInstance()方法,可能创建出N个对象,我们可能会想,这不就是 多 new 些对象的事情嘛,有什么大不了的嘛,其实并不是这样的,对象是有大有小的,有些对象可能会很大,管理的内存数据可能会特别多,如果这个对象管理特别多的内存数据,多 new 几次,内存根本不够呀!所以,线程不安全带来的问题是很严重的!!!

3)解决方式

回顾之前的内容,线程安全问题的措施 如下:

  1. 使用 synchronized 关键字进行加锁,保证操作原子性
  2. 使用 volatile 关键字,可保证内存可见性和禁止指令重排序

通过之前的分析可知:懒汉模式线程不安全是因为多个线程修改同一个变量!进一步分析,引起上述问题的原因是 if判定操作和修改操作是分开的,并不是原子的,显而易见,可以通过加锁来解决这个问题~

这就有一个问题了,锁要加在哪里? 这是值得我们深入思考的,要知道多线程代码是很复杂的,并不说加锁就一定可以解决问题,必须要具体问题具体分析,下面举一个错误的加锁:
在这里插入图片描述
将锁加在了 new 对象的操作上,以类对象为锁对象,这样的加锁方式可行吗?显然是不行的,我们加锁需要保证 if 判定操作和 new 对象操作作为一个整体的,是一个原子操作!才能解决上述问题,而仅把锁加在 new 对象的操作上,仍然不能保证原子性,所以这是错误的加锁方式!!!

1)将 if 操作也放到锁里,保证 if 判定操作和 new 对象操作是一个原子操作

public static SingletonLazy getInstance() { //只有调用这个才会创建对象synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}return instance;}

2)或者直接将锁加在方法上,保证整个方法都是原子的

  synchronized public static SingletonLazy getInstance() { //只有调用这个才会创建对象if (instance == null) {instance = new SingletonLazy();}return instance;}

在前面也讲过,加锁是一个比较低效的操作,因为加锁就可能涉及到阻等待,需坚持非必要,不加锁的原则,在上述加锁方式中存在一个问题:在任何时候调用getInstance()方法都会触发锁竞争!
在这里插入图片描述
事实上,此处的线程不安全问题只是出现在首次创建对象这里,一旦对象 new 好了,后续调用getInstance()方法时,仅仅就是读操作了,不涉及到修改,也就没有线程安全问题了,就没必要加锁了!!!

因此,需要对加锁的位置进行优化,下面具体来介绍如何进行优化的~

a. 优化一:修改锁的位置解决效率问题

问题】到底什么时候需要加锁?
回答】上述分析可得,在首次 new 对象时候需要加锁
措施】需要再加一层 if 判断,用来判断需要加锁的情况

 public static SingletonLazy getInstance() { //只有调用这个才会创建对象//这个if判断是用于判断是否要加锁,如果对象已经有了,此时无需加锁,本身就是线程安全的if(instance == null) {synchronized (SingletonLazy.class) {//这个if判断是如果为空则创建对象if (instance == null) {instance = new SingletonLazy();}}}return instance;}

1)在初心(目的)上,这两个 if 条件看起来是一样的,但是这两个条件的初心即目的是不同的,只是巧了,正好是一模一样的代码
第一个 if 语句目的:判断是用于判读是否要加锁,如果对象已经有了,此时无需加锁,本身就是线程安全的
第二个 if 语句目的:判断 instance 是否为空,如果为空则创建对象
在这里插入图片描述

2)在执行时机上,这两个 if 条件紧挨着,实际上,这两个 if 语句的执行时机有着巨大的差别!

按照我们之前的理解,在单线程代码中,如果两行代码紧挨着,在执行的时候,这两行代码会被迅速执行完,可以近似地看作这两个 if 语句是"同一时机"被执行的

但是在多线程中,上述两个 if 语句中还间隔着一个 synchronized 的情况下,就不能简单地这样理解了
因为加锁就可能导致线程阻塞,而啥时候解除阻塞,无从知晓,可能过了很久才解除阻塞,那么这两行代码虽然看起来是相邻且相同的,但如果调用的时间间隔长了,判断结果也可能会不同!

就比如在一个线程执行时,一开始 instance 为 null,第一个 if 判定成立,进入第一个 if 中,但接下来获取锁时却发现,锁已经被其它线程获取了,那么这个线程此时就只能阻塞等待,等到这个线程结束阻塞,获取到锁的时候,再继续往下执行,发现 instance 已经被别的线程创建好了,不再为 null,第二 if 判断就不成立,此时该线程不会进入到第二个 if 中去,也就不会重复再 new 一个对象

b. 优化二:使用volatile修饰解决 new 操作引发指令重排序

注意!!! 优化后的代码,仍然还存在一个很重要的问题!!! —— 指令重排序,指令重排序也可能导致线程不安全问题
这是怎么一回事呢?回顾之前指令重排序的案例(有些遗忘的,可回顾这期内容)我们一起来分析分析这个代码~

new 的操作大体包括以下3个步骤:

1)申请内存空间
2)调用构造方法,即初始化内存的数据
3)把对象引用赋值给 instance,即内存地址的赋值

在这里插入图片描述这就可能存在指令重排序问题,其中在单线程下步骤2) 和 3) 可以互换顺序,但是在多线程下,如果按照1) 3) 2)的顺序,则可能会出现问题!

假如 instance 为 null,当线程 t1 执行完 1) 和 3) 这两个步骤后,被线程 t2 调度,t2 线程再进入 if 判断时,由于 t1 线程已经申请内存空间并将对象引用赋值给 instance 了,instance 已经不为 null,此时条件不成立,t2 线程中的getInstance()方法则直接返回 instance,实际上 instance 指向的对象还没调用构造方法,即 t2 拿到的是一个没装修过的毛坯房,如果 t2 线程继续往下执行,调用后续的方法,可能就都是将错就错了 !
在这里插入图片描述

尽管上述过程是一个极端小概率情况,但在高并发、大数据的情况下,一旦出现上述问题,后果是十分严重的,不容小视!

解决方式volatile 修饰 instance即可,volatile可禁止指令重排序!

最后懒汉模式整体代码如下:

class SingletonLazy {volatile private static SingletonLazy instance = null; //先置为空public static SingletonLazy getInstance() { //只有调用这个才会创建对象if(instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() { }
}public class ThreadDemo15 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}

【Q】我们知道 volatile 关键字除了禁止指令重排序,还有保证内存可见性的效果,那么在上述代码中,有没有内存可见性问题呢?
【A】暂时保留疑问,在此不作定论
这个代码与之前内存可见性的案例代码差别很大,内存可见性问题发生在由于频繁读,编译器优化掉寄存器从内存读取数据到CPU寄存器的操作,使每一次读数据并没有真正从内存中读取,在上述代码中是否存在频繁读问题,假设 N 个线程一起调用,是否就相当于读了 N 次,触发优化到寄存器中的操作?
这其实是不一定的!!! 每个线程都有自己的一套寄存器,这会不会出现上述问题,无法确定~

4. 面试题 —— 单例模式的线程安全问题

其实就是本期后半部分内容的小结~ 知识点都讲完啦!

【饿汉模式】天然就是线程安全的,因为只是进行读操作
【懒汉模式】是线程不安全的,因为既有读操作,也有写操作

保证懒汉模式线程安全问题的措施:

  1. 加锁,把 if 操作 和 new 操作 变成原子操作
  2. 双重 if,减少不必要的加锁操作,坚持非必要,不加锁的原则
  3. 使用 volatile 禁止指令重排序,保证后续线程拿到的肯定是一个完整的对象

💛💛💛本期内容回顾💛💛💛
在这里插入图片描述
✨✨✨本期内容到此结束啦~

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

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

相关文章

Redis的缓存雪崩,击穿,穿透的介绍

1.缓存雪崩 为保证缓存中的数据与数据库的数据一致,会给Redis里的数据设置一个过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成新的缓存,因为就会访问数据库,并将数据更新到Redis里,这样后续请求就可以直接命中缓存. 当大量缓存在同一时间过期或…

Nginx和Tomcat实现负载均衡群集部署应用

🏡作者主页:点击! 🐧Linux基础知识(初学):点击! 🐧Linux高级管理专栏:点击! 🔐Linux中firewalld防火墙:点击! ⏰️创作时间&…

减少CMOS模拟开关导通电阻引起的失真

1 简介 许多数据采集系统的在多通道间选择时需要使用模拟开关,相比同类的机械开关,半导体开关锁表现出的工作特性是迥然不同的。如:处在闭合位置的CMOS开关的电阻(导通电阻 “Ron”)会因输入电压的不同而改变。该特性通…

IMU的加速度补偿、祛除向心力

目录 1. 简介2. 仅有XY偏移的修正过程3. 3D修正过程 1. 简介 一般,我们期望用IMU测量某个Target坐标系的加速度、角速度信息,然而IMU的坐标系与Target 坐标系一般存在位姿关系,此时IMU测量的加速度不能直接代表Target左坐标系的加速度。比如…

python库(10):SpaCy库实现NLP处理

1 SpaCy简介 自然语言处理(NLP)是人工智能领域中一个重要的分支。它旨在使计算机能够理解、解释和生成人类语言。Python中的SpaCy库提供了丰富的功能和工具,SpaCy是一个开源的软件库,用于处理和操作自然语言文本,可以…

BM42:混合搜索的新基准 - Qdrant

在过去的 40 年里,BM25 一直是搜索引擎的标准。它是一种简单但功能强大的算法,已被许多搜索引擎使用,包括 Google、Bing 和 Yahoo。 虽然看起来向量搜索的出现会削弱其影响力,但效果并不明显。目前最先进的检索方法试图将 BM25 与…

python库(11):Box库简化字典和对象之间的转换

1Box库简介 Box是一个Python库,它提供了一种将数据封装在字典和列表中的方式,同时提供了一些额外的功能,比如数据验证、默认值设置等。这使得Box库非常适合用于配置管理、数据传输对象(DTO)的创建,以及任何…

sqlmap使用之-post注入、head注入(ua、cookie、referer)

1、post注入 1.1、方法一,通过保存数据包文件进行注入 bp抓包获取post数据 将数据保存到post.txt文件 加上-r指定数据文件 1.2、方法二、通过URL注入 D:\Python3.8.6\SQLmap>python sqlmap.py -u "http://localhost/login.php" --data "userna…

替换:show-overflow-tooltip=“true“ ,使用插槽tooltip,达到内容可复制

原生的show-overflow-tooltip“true” 不能满足条件&#xff0c;使用插槽自定义编辑&#xff1b; 旧code <el-table-column prop"reason" label"原因" align"center" :show-overflow-tooltip"true" /> <el-table-column pro…

压缩文件的解析方式

Java中我们用ZipInputStream和ZipOutputStream来完成对zip文件和rar文件的读写 I /O流&#xff1a; Input:输入&#xff0c;通过“输入流”进行文件的读取操作 output:输出&#xff0c;通过“输出流”进行文件的写入操作 一、将压缩包解压缩 1.解压缩.zip格式文件&#xf…

微信小程序毕业设计-汽车维修项目管理系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

NoSQL 之Redis集群模式

一&#xff1a;Redis集群方式 Redis有三种模式&#xff1a;分别是主从复制、哨兵模式、Cluster 1&#xff1a;主从模式: 主从复制是高可用Redis的基础&#xff0c;哨兵和群集都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的…

netscaler LDAP+RADIUS传统的双因素认证方式(之一)

如果使用传统的双因素认证方式&#xff0c;可以通过在Citrix ADC (NetScaler) 13.1上配置Gateway Virtual Server来实现LDAP和RADIUS的双因素认证。当前配置方式&#xff0c;采用Cateway vServer两个Basic Authtication Policy方式实现&#xff0c;以下是详细步骤&#xff1a; …

【码题集】习题

目录 史莱姆融合 松鼠接松果 新月轩就餐 史莱姆融合 根据题意就是一道集合合并的题&#xff0c;所以要用并查集&#xff0c;不过最后我们要输出整个序列&#xff0c;所以要在合并的时候维护一个链表&#xff0c;以便最终合并成一个大集合的时候&#xff0c;输出整个链表就是…

Kotlin Misk Web框架

Kotlin Misk Web框架 1 添加依赖1.1 build.gradle.kts1.2 settings.gradle.kts1.3 gradle.properties 2 请求接口3 程序模块4 主服务类5 测试结果 Misk 是由 Square 公司开发的一个开源的多语言服务器框架&#xff0c;主要用于构建微服务。它主要使用 Kotlin 语言&#xff0c;但…

UGC与AI引领的下一个10年,丝芭传媒已经准备好

丝芭传媒最近传来的消息&#xff0c;都跟技术相关。 基于自研AI大模型“Paro&#xff08;心乐舞河&#xff09;”的AIGPT及AIGC生成工具APP“鹦鹉人”开启用户内测。2023年3月技术测试的图形化智能社交基座“美踏元宇宙”&#xff0c;也将开放首轮用户内测。 此外&#xff0c…

Studying-代码随想录训练营day31| 56.合并区间、738.单调递增的数字、968.监控二叉树、贪心算法总结

第31天&#xff0c;贪心最后一节(ง •_•)ง&#x1f4aa;&#xff0c;编程语言&#xff1a;C 目录 56.合并区间 738.单调递增的数字 968.监控二叉树 贪心算法总结 56.合并区间 文档讲解&#xff1a;代码随想录合并区间 视频讲解&#xff1a;手撕合并区间 题目&#xf…

高效图纸管理:彩虹图纸管理软件助您一臂之力

高效图纸管理&#xff1a;彩虹图纸管理软件助您一臂之力 在制造业的激烈竞争中&#xff0c;高效图纸管理是企业提升竞争力和降低成本的关键。然而&#xff0c;传统的图纸管理方式往往存在效率低下、信息混乱等问题。此时&#xff0c;彩虹图纸管理软件凭借其卓越的性能和丰富的功…

一个vue页面复用方案

前言 问大家一个问题&#xff0c;曾经的你是否也遇到过&#xff0c;一个项目中有好几个页面长得基本相同&#xff0c;但又差那么一点&#xff0c;想用 vue extends 继承它又不能按需继承html模板部分&#xff0c;恰好 B 页面需要用的 A 页面 80% 的模板&#xff0c;剩下的 20%…

【验收支撑】软件系统验收计划书(直接套用原件doc)

编写软件验收计划是软件开发过程中的一个关键步骤&#xff0c;其重要性体现在以下几个方面&#xff1a; 明确验收标准&#xff1a;软件验收计划详细列出了验收的标准、测试方法、测试环境等&#xff0c;确保所有相关人员对验收的期望和要求有清晰的认识。这有助于避免在验收阶段…