第二章创建模式—单例设计模式
文章目录
- 单例模式的结构
- 如何控制只有一个对象呢
- 怎么设计这个类的内部对象
- 外部怎么访问
- 单例模式的主要有以下角色
- 单例模式的实现
- 饿汉式 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)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...