并发编程
文章平均质量分 90
并发编程
swadian2008
不积跬步,无以至千里;不积小流,无以成江海
展开
-
并发编程7:线程池的使用
如果没有线程正在等待,并且线程池的当前大小小于最大值,那么ThreadPoolExecutor 将创建一个新的线程,否则根据饱和策略,这个任务将被拒绝。它提供了大量可调节的选项,例如创建线程和关闭线程的策略,处理队列任务的策略,处理过多任务的策略,并且提供了几个钩子方法来扩展它的行为。同样,当线程池中的任务是数据库连接的唯一使用者时,那么线程池的大小又将限制连接池的大小。如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过了基本大小时,这个线程将被终止。原创 2023-09-07 17:15:30 · 214 阅读 · 0 评论 -
并发编程6:任务或线程的取消与关闭
Java 提供了中断 (Interruption)协作机制,通过该机制能够使一个线程终止另一个线程的当前工作。这种协作式的方法是必要的,我们很少希望某个任务、线程或服务立即停止,因为这种立即停止会使共享的数据结构处于不一致的状态。相反,在编写任务和服务时可以使用一种协作的方式:当需要停止时,它们首先会清除当前正在执行的工作,然后再结束。这提供了更好的灵活性,因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作。//中断操作应该由中断线程本身去决定何时中断。原创 2023-09-04 10:47:47 · 365 阅读 · 0 评论 -
并发编程5:如何执行任务?
大多数并发应用程序都是围绕来构造的:任务通常是一些抽象的且离散的工作单元。通过把应用程序的工作分解到多个任务中,可以简化程序的组织结构,提供一种事务边界来优化错误恢复过程,以及提供一种并行工作结构来提升并发性。//目的:如何把一个工作拆解成多个任务,并发执行->清晰的任务边界(独立任务有利于并发)原创 2023-08-23 14:09:01 · 567 阅读 · 0 评论 -
并发编程4:Java 中的并发基础构建模块
委托是创建线程安全类的一个最有效的策略:只需让现有的线程安全类管理所有的状态即可。Java 平台类库包含了丰富的并发基础构建模块,例如线程安全的容器类以及各种用于协调多个相互协作的线程控制流的同步工具类(Synchronizer)等。Java 并发类的 API 文档,。原创 2023-08-17 17:38:45 · 173 阅读 · 0 评论 -
并发编程3:如何设计线程安全的类
上边代码稍微改变了车辆追踪器类的行为,在使用监视器模式的车辆追踪器中返回的是车辆位置的快照,而在使用委托的车辆追踪器中返回的是一个不可修改但却实时的车辆位置视图。它并不关心底层的 List 是否是线程安全的,即使 List 不是线程安全的或者修改了它的加锁实现,ImprovedList 也会提供一致的加锁机制来实现线程安全性。为了防止多个线程在并发访问同一个对象时产生的相互干扰,这些对象应该要么是线程安全的对象,要么是事实不可变的对象,或者由锁来保护的对象。原创 2023-08-09 15:37:23 · 140 阅读 · 0 评论 -
并发编程2:如何进行对象共享?
“发布(Publish)”一个对象的思是指,使对象能够在当前作用域之外的代码中使用。例如,将一个指向该对象的引用保存到其他代码可以访问的地方,或者将引用传递到其他类的方法中。发布内部状态可能会破坏封装性,并使得程序难以维持不变性条件。例如,如果在对象构造完成之前就发布该对象,就会破坏线程安全性。当某个不应该发布的对象被发布时,这种情况就被称为逸出( Escape)。原创 2023-08-08 15:38:52 · 156 阅读 · 0 评论 -
并发编程1:线程安全性概述
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。//所见即所知。原创 2023-08-07 17:27:21 · 140 阅读 · 0 评论 -
Fork/Join 框架详解
Fork/Join 是一种并行计算模式,用于解决可以被分解成更小的可并行任务的问题。该模式包含两个关键操作:Fork(分解)和Join(合并)。在 Fork/Join 模式中,原始问题被递归地分解为更小的子问题,直到达到可以并行解决的最小单位。这个过程被称为 Fork。每个子问题可以独立地在不同的处理器上执行,并行地求解部分问题。一旦所有的子问题都被解决,就会进行 Join 操作。Join 操作将所有子问题的结果合并为最终的解决方案。原创 2023-05-16 11:30:10 · 3329 阅读 · 0 评论 -
Java 线程池(Thread Pools)详解
线程池是一种重用线程的机制,用于提高线程的利用率和管理线程的生命周期,常用于多线程编程和异步编程。Java提供了多种线程池实现,其中最常用的是ThreadPoolExecutor类和Executors类提供的静态工厂方法。线程池由一个线程队列和一个任务队列组成,线程队列中保存着空闲线程,任务队列中保存着等待执行的任务。线程池启动后,线程池中的线程从任务队列中获取任务并执行,执行完毕后返回线程队列中等待下一次任务的到来。如果任务队列为空,线程池中的线程将等待新的任务到来或被关闭。原创 2023-05-15 15:02:07 · 7245 阅读 · 2 评论 -
Java Executor接口详解
Executor是Java中的一个接口,用于执行Runnable任务。该接口只定义了一个方法execute(Runnable command),用于将任务提交到执行器中执行。Executor接口没有定义线程池的概念,因此使用Executor只能创建一个新的线程执行任务,而不能重用线程。原创 2023-05-08 17:07:40 · 1773 阅读 · 0 评论 -
Java 中的不可变对象(Immutable Objects)详解
不可变对象指的是在创建之后,它们的值无法被修改的对象。这意味着如果想改变一个不可变对象的值,就需要创建一个新的对象。在许多编程语言中,字符串和数字都是不可变的对象。这个新字符串就是一个全新的字符串对象,和原来的字符串对象没有关系。不可变对象的一个好处是它们更容易被并发程序安全地使用。因为不可变对象是无法修改的,所以不需要担心在多线程环境下出现竞态条件的问题。此外,不可变对象也更容易进行缓存和复用。原创 2023-05-06 16:39:06 · 2657 阅读 · 0 评论 -
Java 中的并发工具类详解:Semaphore、CountDownLatch 和 CyclicBarrier
Java 并发包提供了哪些并发工具类?比 synchronized 更加高级的各种同步结构各种线程安全的容器各种并发队列实现强大的 Executor 框架// 同步等待// 同步等待// 信号量1、信号量:Semaphore你可以想象一下这个场景,在车站、机场等出租车时,当很多空出租车就位时,为防止过度拥挤,调度员指挥排队等待坐车的队伍一次进来 5 个人上车,等这 5 个人坐车出发,再放进去下一批,这和 Semaphore 的工作原理有些类似。原创 2022-09-29 15:09:01 · 557 阅读 · 0 评论 -
深入理解CAS
目录1、什么是CAS?2、使用Java中的CAS3、CAS源码分析4、原子类和CAS自旋锁的实现5、ABA 问题1、什么是CAS?CAS(Compare And Swap,比较并交换),通常指的是这样一种原子操作:针对一个变量,首 先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。 CAS 的逻辑用伪代码描述如下:if (value == expectedValue) { // 1-比较 value = newValue; // 2-交换}以原创 2022-05-30 16:08:12 · 294 阅读 · 0 评论 -
Java 线程是如何创建的?创建原理详解
协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行),具有对内核来说不可见的特 性。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。// go语言有协程机制操作系统协程的特点在于是一个线程执行。那么和多线程比,协程有何优势?线程的切换由操作系统调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。线程的默认stack大小是1M,而协程更轻量,接近1k。原创 2022-05-24 19:33:11 · 796 阅读 · 0 评论 -
进程、线程以及上下文切换概念详解
上下文切换指的是在计算机中,当多个进程或线程共享同一个处理器时,需要在它们之间进行切换的过程。在一个时刻,CPU只能执行一个进程或线程的代码,当需要切换到另一个进程或线程时,需要保存当前进程或线程的执行状态,包括程序计数器、寄存器和内存指针等,然后将这些状态还原到另一个进程或线程中,使得它们能够接着之前的执行位置继续执行。这个过程就称为上下文切换。上下文切换通常发生在以下情况:// 上下文切换是由操作系统内核来执行的当一个进程或线程因为等待输入、输出、锁或其他原因被阻塞。原创 2022-05-24 19:30:27 · 2902 阅读 · 0 评论 -
Java Atomic 原子操作类
目录1、基本类型的原子更新2、数组类型的原子更新3、引用类型的原子更新4、对象属性原子修改器5、LongAdder/DoubleAdder详解6、LongAdder 原理在并发编程中很容易出现并发安全的问题,有一个很简单的例子就是多线程更新变量 i=1 ,比如多个线程执行 i++ 操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过 Synchronized 进行控制来达到线程安全的目的。但是由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方.原创 2022-06-04 20:54:10 · 514 阅读 · 0 评论 -
Java并发线程池底层原理剖析和源码分析
目录一、线程和线程池的效率对比二、Java线程池三、线程池源码分析四、ScheduledThreadPoolExecutor源码分析一、线程和线程池的效率对比使用线程方式执行程序/*** * 使用线程的方式去执行程序 */public class ThreadTest { public static void main(String[] args) throws InterruptedException { Long start = System.原创 2022-05-22 16:23:37 · 326 阅读 · 0 评论 -
NO.9 Lock和Condition的使用
在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。Java SDK 并发包通过 Lock 和 Condition 两个接口来实现管程,其中 Lock 用于解决互斥问题,Condition 用于解决同步问题。一、为什么要再造管程?我们前面在介绍死锁问题的时候,提出了一个破坏不可抢占条件方案,但是这个方案 synchronized 没有办法解决。原因是 synchronized 申请资源的时候,如果申请不到,线程直接进入阻塞状态原创 2021-10-09 22:42:37 · 470 阅读 · 0 评论 -
NO.8 Java线程的生命周期详解
目录一、通用的线程生命周期二、Java 中线程的生命周期RUNNABLE 与 BLOCKED 的状态转换RUNNABLE 与 WAITING 的状态转换RUNNABLE 与 TIMED_WAITING 的状态转换从 NEW 到 RUNNABLE 状态从 RUNNABLE 到 TERMINATED 状态stop() 和 interrupt() 方法的主要区别是什么呢?Java 语言里的线程本质上就是操作系统的线程,它们是一一对应的。虽然不同的开发语言对于操作系统线程进行了不原创 2021-10-09 22:41:53 · 236 阅读 · 0 评论 -
NO.7 Monitor(管程)是什么意思?Java中Monitor(管程)的介绍
Monitor(管程)是什么意思?Java中Monitor(管程)的介绍Monitor(管程)是什么意思?Java中Monitor(管程)的介绍_sinolover的专栏-CSDN博客_管程是什么意思本篇文章给大家带来的内容是关于Monitor(管程)是什么意思?Java中Monitor(管程)的介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。monitor的概念管程,英文是 Monitor,也常被翻译为“监视器”,monitor 不管是翻译为“管程”还是“监视器”,都是比较晦...原创 2021-10-09 22:40:56 · 1596 阅读 · 2 评论 -
Java 线程中得安全性、活跃性及性能问题详解
有时线程虽然没有发生阻塞,但仍然会存在执行不下去的情况,这就是所谓的“活锁”。可以类比现实世界里的例子,路人甲从左手边出门,路人乙从右手边进门,两人为了不相撞,互相谦让,路人甲让路走右手边,路人乙也让路走左手边,结果是两人又相撞了。这种情况,基本上谦让几次就解决了,因为人会交流啊。可是如果这种情况发生在编程世界了,就有可能会一直没完没了地“谦让”下去,成为没有发生阻塞但依然执行不下去的“活锁”。// 活锁的形象场景,线程相互谦让解决“活锁”的方案很简单,谦让时,尝试等待一个随机的时间就可以了。原创 2021-10-09 22:39:51 · 268 阅读 · 0 评论 -
Java 线程之间的通信和等待通知机制详解
Guarded Blocks 是一种线程间通信的方式,它能够确保某个条件成立时才能执行相应的操作。Guarded Blocks 的基本思想是:线程在循环中等待某个条件成立,一旦条件成立,就执行相应的操作。它的主要优点是能够避免线程在等待条件成立时浪费 CPU 资源,同时也能避免条件被错误地设置。// 线程经常需要协调它们的动作。最常见的协调方式就是保护块。在共享对象上加锁,以确保线程互斥地访问共享对象。原创 2021-10-09 22:38:01 · 414 阅读 · 0 评论 -
NO.4 如何预防死锁?
一、死锁的出现我们试想在古代,没有信息化,账户的存在形式真的就是一个账本,而且每个账户都有一个账本,这些账本都统一存放在文件架上。银行柜员在给我们做转账时,要去文件架上把转出账本和转入账本都拿到手,然后做转账。这个柜员在拿账本的时候可能遇到以下三种情况:文件架上恰好有转出账本和转入账本,那就同时拿走; 如果文件架上只有转出账本和转入账本之一,那这个柜员就先把文件架上有的账本拿到手,同时等着其他柜员把另外一个账本送回来; 转出账本和转入账本都没有,那这个柜员就等着两个账本都被送回来。这个逻辑原创 2021-09-22 18:38:34 · 269 阅读 · 0 评论 -
NO.3 互斥锁-解决原子性问题和保护多个资源问题
目录一、解决原子性问题——互斥1、那原子性问题到底该如何解决呢?2、简易锁模型3、改进后的锁模型4、Java 语言提供的锁技术:synchronized5、用 synchronized 解决 count+=1 问题6、锁和受保护资源的关系二、如何用一把锁保护多个资源?1、保护没有关联关系的多个资源2、保护有关联关系的多个资源3、使用锁的正确姿势一、解决原子性问题——互斥一个或者多个操作在 CPU 执行的过程中不被中断的特性,称为“原子性”。1、那原子性原创 2021-09-15 13:43:12 · 192 阅读 · 0 评论 -
Java 是如何解决并发时的可见性和有序性问题的?
我们知道,导致可见性的原因是缓存,导致有序性的原因是编译优化,那解决可见性、有序性最直接的办法就是禁用缓存和编译优化,但是这样问题虽然解决了,程序的性能可就堪忧了。合理的方案应该是按需禁用缓存以及编译优化。那么,如何做到“按需禁用”呢?对于并发程序,何时禁用缓存以及编译优化只有程序员知道,那所谓“按需禁用”其实就是指按照程序员的要求来禁用。所以,为了解决可见性和有序性问题,只需要提供给程序员按需禁用缓存和编译优化的方法即可。// 解决方案居然是禁用,看起来挺直接简单的。原创 2021-09-14 13:33:28 · 164 阅读 · 0 评论 -
多线程中的可见性、原子性、有序性问题详解
要写好并发程序,首先要知道并发程序的问题在哪里,只有确定了“靶子”,才有可能把问题解决,毕竟所有的解决方案都是针对问题的。并发程序经常出现的诡异问题看上去非常无厘头,但是深究的话,无外乎就是直觉欺骗了我们,只要我们能够深刻理解可见性、原子性、有序性在并发场景下的原理,很多并发 Bug 都是可以理解、可以诊断的。在介绍可见性、原子性、有序性的时候,提到缓存导致的可见性问题,线程切换带来的原子性问题,编译优化带来的有序性问题,其实缓存、线程、编译优化的目的和我们写并发程序的目的是相同的,都是提高程序性能。原创 2021-09-12 14:04:49 · 260 阅读 · 0 评论 -
如何正确停止一个线程?Java 线程的中断机制详解
非静态的 isInterrupted() 方法被一个线程用来查询另一个线程的中断状态,它不会改变中断标志的状态。处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理(由程序员决定线程如何响应中断)。在扫描文件的过程中,对于中断的检测这里采用的策略是,修改上面的代码,线程执行任务期间有休眠需求:;原创 2021-09-09 20:23:20 · 1417 阅读 · 0 评论 -
join(),sleep() 和 yeild() 方法的区别
如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到 join() 方法。当 A 需要等待 B 执行完,此时 A 设置 1 小时的等待时间(睡眠 1 小时),但是 B 只用了 30 分钟便执行完了,此时可以在 B 上执行 A 的中断方法,使 A 继续执行,因此,异常也是可以被利用的。,其它线程可以使用 interrupt() 方法打断正在睡眠的线程,这时 sleep() 方法会抛出 InterruptedException,并且。类直接提供的方法,而。原创 2021-08-09 20:04:17 · 841 阅读 · 0 评论 -
守护线程和用户线程的区别
在 Java 中,可以使用 Thread 类的 setDaemon() 方法将一个线程设置为守护线程,也可以使用 isDaemon() 方法判断一个线程是否为守护线程。通常来说,守护线程经常被用来执行一些后台任务,但是呢,你又希望在程序退出时,或者说 JVM 退出时,线程能够自动关闭,此时,守护线程是你的首选。需要注意的是,如果程序中的所有线程都是守护线程,那么虚拟机将立即退出。是虚拟机启动的线程中的普通线程,当所有用户线程结束运行后,虚拟机才会停止运行,即使还有一些守护线程在运行。原创 2021-08-02 18:28:33 · 976 阅读 · 0 评论 -
Java 中的线程局部变量ThreadLocal和InheritableThreadLocal分析
ThreadLocal变量是Java中的一种线程局部变量,它为每个线程提供了独立的变量副本。在多线程环境下,每个线程可以独立地访问和修改自己的ThreadLocal变量副本,而不会对其他线程产生影响。// Thread类中的一个属性每个ThreadLocal对象都维护着一个独立的变量副本,这些副本存储在ThreadLocal对象的内部数据结构中,通常是一个Map。当线程访问ThreadLocal变量时,实际上是通过当前线程获取到自己的变量副本。原创 2021-07-12 14:34:16 · 217 阅读 · 0 评论 -
Java线程池和SpringBoot异步线程池
目录一、SpringBoot异步线程池1、定义线程池2、线程池的使用二、ThreadPoolTaskExecutor和ThreadPoolExecutor区别1、ThreadPoolExecutor的处理流程 2、四种Reject预定义策略三、Java线程池1、使用线程池的优势2、什么是阻塞队列?3、线程池为什么要是使用阻塞队列?4、如何配置线程池?...原创 2020-04-16 18:57:23 · 2109 阅读 · 0 评论 -
Java执行线程任务的三种方式(带案例)
目录一、线程、进程和内存二、Java执行线程任务的三种方式1、直接继承Thread2、实现Runnable接口3、实现Callable接口一、线程、进程和内存线程和进程进程是系统进行资源分配和调度的基本单位,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。线程是进程中的一个实体,线程是不会独立存在的! 所以说,没有进程就就没有线程。对于CPU资源比较特殊,线程才是CPU分配的基本单位。main函数启动 ——>JVM进程 ——>main函数线程原创 2019-08-10 15:26:22 · 3119 阅读 · 0 评论 -
Java Thread 对象详解
Java中的Thread类是一个非常重要的类,用于创建和管理线程。下面是Thread类的一些重要方法和属性的详细解释:Thread()start() 方法是启动线程的方法,它将启动一个新线程并调用run() 方法。start() 方法不能被重复调用,否则将抛出IllegalThreadStateException 异常。run()方法是线程的执行体,它必须被子类覆盖。当调用start()方法后,新线程会执行其run()方法中的代码。方法会使当前线程睡眠指定的毫秒数,让出CPU的使用权。原创 2019-08-10 16:28:36 · 280 阅读 · 1 评论 -
多线程编程(三)——线程的状态,修改中
目录状态总述和图解一、线程状态——NEW二、线程状态——RUNNABLE三、线程状态——BLOCKED四、线程状态——WAITING五、线程状态——TIME_WATING(限时等待)六、线程状态——TERMINATEDBLOCKED——等待锁测试(待验证)isAlive()方法状态总述和图解JAVA线程总共有六种状态:NEW、RUNNABLE、BLOCK...原创 2019-08-10 22:20:51 · 292 阅读 · 0 评论 -
如何停止一个线程?优雅终止和暴力终止对比
其一,使线程正常退出,也就是当run()方法完成后线程终止;其二,使用异常,通过抛出异常来终止操作。2、。使用stop和suspend及resume方法,当都是过期方法,非常容易引发线程安全问题,不推荐使用。// 通过本文分析,可以更好的去理解Java为什么会使用打标记的方式去停止线程,而不是立即让线程停止。这种方式,实际上是Java把终止的权力过渡给了程序员自己去处理,因为,只有写程序的人才知道线程应该什么时候停止,所以把终止线程的权力给程序员控制是合理的。原创 2019-08-11 18:32:21 · 1060 阅读 · 2 评论 -
Java 线程间的通信机制详解
目录1、volatile 关键字2、等待唤醒(等待通知)机制(1)wait() 和 notify() 等待唤醒(2)park() 和 unpark() 等待唤醒3、join() 方法——线程排队4、管道输入输出流5、ThreadLocal类和InheritableThreadLocal类线程间通信使线程成为一个整体,提高系统之间的交互性,在提高CPU利用率的同时可以对线程任务进行有效的把控与监督。volatile有两大特性,一是可见性,二是有序性,禁止指令重排序。其中可见性就是可以让线程之间进行通信。以下两原创 2019-08-21 23:45:07 · 2710 阅读 · 1 评论 -
Synchronized 关键字详解
synchronized 是 Java 中的一种同步机制,用于实现线程之间的互斥和同步。当一个线程需要访问一个对象的同步代码块时,如果这个同步代码块已经被另一个线程所占用,那么当前线程就会被阻塞,直到另一个线程执行完毕并释放锁。只有获取到锁的线程才能执行同步代码块。使用关键字修饰一个方法时,该方法被称为同步方法。当一个线程访问一个对象的同步方法时,其他线程将被阻塞,直到该线程执行完毕并释放锁。同步方法的锁对象是当前实例对象。使用关键字修饰一段代码时,称该代码块为同步代码块。原创 2019-08-17 17:34:50 · 623 阅读 · 1 评论 -
多线程编程(六)——volatile关键字和原子类
目录一、有序性、可见性、原子性1、有序性2、可见性3、原子性二、volatile关键字的作用——可见性1、Volatile关键字使用条件2、Volatile关键字使用场景3、Volatile关键字使用缺点4、Volatile关键字和synchronized关键字的比较5、volatile的非原子性三、原子类——原子性1、原子类使用场景(1)多线程...原创 2019-08-17 22:19:13 · 847 阅读 · 1 评论 -
Java 中的Lock锁对象(ReentrantLock/ReentrantReadWriteLock)详解
当有线程获取读锁时,如果当前没有线程持有写锁,则读锁将立即被获取,读锁计数器加一,表示当前有一个线程持有读锁。在 getValue() 方法中,线程会获取锁,返回共享变量 value 的值,最后释放锁。例如,如果一个线程在尝试获取锁的过程中遇到了饥饿现象(即一直获取不到锁),那么系统可能会采用类似于睡眠的方式,让该线程暂时让出 CPU 资源,等待一段时间后再次尝试获取锁。接着,它获取读锁,释放写锁,这样就实现了锁降级。这是因为写锁是独占锁,不能被多个线程同时持有,而读锁是共享锁,可以被多个线程同时持有。原创 2019-08-29 21:49:36 · 3265 阅读 · 1 评论 -
Java 中的定时器类Timer详解
Timer是Java中的一个定时器类,用于在指定时间之后执行某些任务,也可以在指定时间间隔内周期性地执行某些任务。它是在JDK 1.3中引入的。schedule(TimerTask task, Date time):该方法用于在指定时间执行指定的任务。schedule(TimerTask task, long delay):该方法用于在指定延迟时间之后执行指定的任务。原创 2019-08-31 17:15:16 · 1981 阅读 · 1 评论