详解设计模式:单例的进化之路

概念

单例模式(Singleton Pattern)是设计模式中一个重要的模式之一,是确保一个类在任何情况下都绝对只有一个实例。单例模式一般会屏蔽构造器,单例对象提供一个全局访问点,属于创建型模式。

根据初始化时间的不同,可以将单例模式分为两类:

  • 饿汉式单例
  • 懒汉式单例

当然,除了上面的两个分类之外,处于对性能、安全等方面的考量,单例模式还演化出了各种实现版本,每一种版本的演进,都是单例的一次**进化与升级,**下面就来看看单例模式的进化之路上都经历了哪些挑战与对抗。

饿汉式单例

饿汉式单例,特指在 类加载的时候就立即初始化并创建单例对象的一种单例模式写法。由于是在线程还没有出现之前就被实例化了,所以这种模式下的单例是线程绝对安全的,不存在访问安全的问题。

根据具体的实现方式划分,饿汉式单例可以通过 私有化构造器以及 使用静态代码块的方式具体实现。

  • 私有化构造器写法:HungrySingleton.java
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:32
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 饿汉式单例-构造器私有化写法
 */
public class HungrySingleton {
    private static final HungrySingleton hungrSingleton  = new HungrySingleton();

    private HungrySingleton() {};

    public static HungrySingleton getInstance() {
        return hungrSingleton;
    }
}

上面的代码中,我们将构造器进行了私有化之后,无法再通过new来创建对象,这种实现下,只能通过提供的getInstance()方法来获得单例对象。

  • 静态代码块写法:HungryStaticSingleton.java
package 设计模式.单例模式.饿汉式单例;

/**
 * @author: 八尺妖剑
 * @date: 2022/4/23 8:36
 * @description: 饿汉式单例静态块写法
 * @blog:www.waer.ltd
 */
@SuppressWarnings({"all"})
public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungryStaticSingleton;


    static {
        hungryStaticSingleton = new HungryStaticSingleton();
    }

    private HungryStaticSingleton(){

    }

    public static HungryStaticSingleton getInstance(){
        return hungryStaticSingleton;
    }
}
  • 测试类
package ltd.waer.javabaseforio.PatternDesign;

@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:42
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 饿汉式单例测试类
 */
public class HungrySingletonTest {
    public static void main(String[] args) {
        //私有构造器写法
        HungrySingleton hungrySingleton1 = HungrySingleton.getInstance();
        HungrySingleton hungrySingleton2 = HungrySingleton.getInstance();
        System.out.println(hungrySingleton1 == hungrySingleton2);

        //静态块初始化写法
        HungryStaticSingleton singleton3 = HungryStaticSingleton.getInstance();
        HungryStaticSingleton singleton4 = HungryStaticSingleton.getInstance();
        System.out.println(singleton3==singleton4);
    }
}

测试结果:true。说明两种方式实现的单例都是有效的,因为不论我们调用多少次 getInstance 方法最后返回的就是同一个对象

优缺点:

创建的对象没有添加任何锁,执行效率高。

由于是在类加载的时候就初始化了,所以不管我们使用与否,它都将占有一定的内存空间,这种情况下,通过项目中存在了大量的单例,那么所占用的内存量就很可观了,着实浪费。

懒汉式单例

那么针对上述饿汉式单例存在的空间占用问题,有没有合适的替换或者解决方案呢?那么有请懒汉出场。
见名知意, 懒汉式单例饿汉式单例的理念刚好相反。它不会在 类加载的时候就初始化,而是等到用到了才会初始化,就这点来说,确实很 懒汉,不饿不吃饭(似乎有点道理??我不饿的时候也不想吃饭)。

到这里,单例模式就开始自己的进化之路了,下面列一下进化路线

进化主线:

  • 普通非线程安全单例
    • sync线程安全单例
      • 双重检查锁单例
        • 内部类单例
          • 枚举式单例

打野副本:

  • 内部类单例
    • 注册式单例
    • 单线程安全单例
      • 枚举式单例

1. 普通非线程安全单例

package ltd.waer.javabaseforio.PatternDesign.LazySingleton;
@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:47
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 版本一:非线程安全
 */
public class LazySingleton {
   private static LazySingleton lazySingleton = null;

   private LazySingleton() {

   };

   public static LazySingleton getInstance() {
       if (null == lazySingleton) {
           lazySingleton = new LazySingleton();
       }
       return lazySingleton;
   }

    public static void main(String[] args) {
        LazySingleton instance1 = LazySingleton.getInstance();
        LazySingleton instance2 = LazySingleton.getInstance();
        System.out.println(instance1 == instance2);
    }
}

上面是单例的最简单写法,也是最初的一种版本,在开始时将实例赋值为null,并没有进行初始化,而是在调用getInstance方法的时候才会初始化,虽然实现简单,但也存在线程安全问题,多线程环境下有一定几率会返回多个单例对象,这显然违背了单例的原则,进一步的解决办法就是下面这种实现。使用synchronizeed关键字保证线程安全。

2. sync线程安全单例

package ltd.waer.javabaseforio.PatternDesign.LazySingleton;
@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:51
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 线程安全的懒汉式单例-synchronized
 */
public class LazySyncSingleton {
    private static LazySyncSingleton lazySyncSingleton = null;

    private LazySyncSingleton() {};

    public synchronized LazySyncSingleton getInstance () {
        if (null == lazySyncSingleton) {
            lazySyncSingleton = new LazySyncSingleton();
        }
        return lazySyncSingleton;
    }
}

上面的实现也非常简单,在前面一种写法的基础山加了一个synchronized关键字即可,这样确实解决了线程安全的问题,但也引出了一个新的问题,假如单例对象的创建非常复杂耗时的情况下,一旦并发量上来了,CPU压力上升,那么可能会导致大批量线程出现阻塞的情况,从而导致程序的允许性能大幅下降,解决方法是**双重检查锁(double-checked locking)**单例写法,如下:

3. 双重检查锁单例

package ltd.waer.javabaseforio.PatternDesign.LazySingleton;
@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:59
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 解决写法2的问题,双重检查锁写法
 */
public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    private LazyDoubleCheckSingleton () {

    };

    public static LazyDoubleCheckSingleton getInstance() {
        if ( null == lazyDoubleCheckSingleton) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (null == lazyDoubleCheckSingleton) {
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

这种写法中,较于上面的写法做了两个地方的改变。

  • lazyDoubleCheckSingleton属性加上了volatile关键字,原因就是为了解决多线程下可见性问题,因为我们的getInstance方法在判断lazyDoubleCheckSingleton是否为null时并没有加锁,所以假如线程1初始化了对象,另外线程2是无法感知的,而加上了volatile之后便可以解决这个问题。

  • synchronized关键字移到了方法内部,尽可能缩小加锁的代码块,提升效率

迭代了这几个版本,到这里是否就已经完美了呢?其实不是,这种写法依旧存在问题,那就是指令重排问题。

上面new对象只有一行代码,然而这行代码在JVM底层却分成了3步:

  1. 分配内存来创建对象,即new操作。
  2. 创建一个对象lazyDoubleCheckSingleton此时lazyDoubleCheckSingleton==nul
  3. new出来的对象赋给lazyDoubleCheckSingleton

但实际运行的时候为了提升效率,这3步并不会按照实际顺序来运行。

假如线程t1进入同步代码块正在创建对象,而此时执行了后面2步,也即是此时lazyDoubleCheckSingleton依已经不为null了,但是对象却没有创建结束,这时候又来了一个线程t2进入getInstance方法,这时候if条件不再成立,线程t2会直接返回一个残缺不全的对象,自然会出现报错。

为了解决这个问题,下面引出了第四个单例版本,即

4. 内部类单例

package ltd.waer.javabaseforio.PatternDesign.LazySingleton;
import java.io.Serializable;
import java.lang.reflect.Constructor;
@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 10:48
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 内部类懒汉式单例-解决指令重排问题
 */
public class LazyInnerClassSingleton  implements Serializable {
    private LazyInnerClassSingleton () {

    };

    public static final LazyInnerClassSingleton getInstance() {
        return InnerLazy.LAZY;
    }

    private static class InnerLazy {
        private static final LazyInnerClassSingleton LAZY =new LazyInnerClassSingleton();
    }
}

这种写法巧妙的利用了内部类会等到外部调用时才会被初始化的特性,用饿汉式单例的思想实现了懒汉式单例。

这种写法看起来已经是高效完美,但其实存在安全隐患,比如可以通过反射的方式破坏这种写法,测试代码如下:

public static void main(String[] args) throws Exception {
    Class<?> clazz = LazyInnerClassSingleton.class;
    Constructor constructor = clazz.getDeclaredConstructor();
    constructor.setAccessible(true);
    Object o1 = constructor.newInstance();
    Object o2 = LazyInnerClassSingleton.getInstance();
    System.out.println(o1 == o2); //false
}

可以看到,虽然构造方法被私有化了,但是我们仍然可以利用反射来破坏单例。为了防止反射破坏单例,我们将上面的写法再改造一下。

5. 改进版的内部类单例

public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton(){
        //防止反射破坏单例
         if(null != InnerLazy.LAZY){
           throw new RuntimeException("不允许通过反射类构造单例对象");
         }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }

    private static class InnerLazy{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

尽管如此,但假如我们的单例对象实现了 Serializable 接口,那么内部类的写法就还是能通过序列化来破坏

6. 实现了Serializable接口的内部类单例

package singleton.lazy;

import java.io.Serializable;

public class LazyInnerClassSingleton implements Serializable {

    private LazyInnerClassSingleton(){
        //防止反射破坏单例
         if(null != InnerLazy.LAZY){
           throw new RuntimeException("不允许通过反射类构造单例对象");
         }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }

    private static class InnerLazy {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

由于实现了序列化的接口,所以内部类的写法依然可以通过序列化来进行破坏,比如使用下面这段测试代码。

package singleton.lazy;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TestLazyInnerClassSingleton2 {
    public static void main(String[] args) {
        //序列化攻击内部类式单例
        LazyInnerClassSingleton s1 = null;
        LazyInnerClassSingleton s2 = LazyInnerClassSingleton.getInstance();

        FileOutputStream fos = null;

        try {
            fos = new FileOutputStream("LazyInnerClassSingleton.text");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("LazyInnerClassSingleton.text");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (LazyInnerClassSingleton)ois.readObject();
            ois.close();
            System.out.println(s1 == s2);//输出:false
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

上面示例中 s1 是通过我们自己提供的全局入口创建的对象,而 s2 是通过序列化的方式创建的对象,不相等说明这是两个对象,也就是说序列化破坏了单例模式。

解决办法就是在 LazyInnerClassSingleton 类中加一个 readResolve 方法,防止序列化破坏单例。

7. 再改进版的内部类单例

package singleton.lazy;

import java.io.Serializable;

public class LazyInnerClassSingleton implements Serializable {

    private LazyInnerClassSingleton(){
        //防止反射破坏单例
         if(null != InnerLazy.LAZY){
           throw new RuntimeException("不允许通过反射类构造单例对象");
         }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }

    private static class InnerLazy {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
    
    //防止通过序列化破坏单例
    private Object readResolve(){
        return InnerLazy.LAZY;
    }
}

这次返回了 true,也就是序列化没有破坏单例了。原因是因为 JDK 源码中在序列化的时候会检验一个类中是否存在一个 readResolve 方法,如果存在,则会放弃通过序列化产生的对象,而返回原本的对象。

这种方式虽然保证了单例,但是在校验是否存在 readResolve 方法前还是会产生一个对象,只不过这个对象会在发现类中存在 readResolve 方法后丢掉,然后返回原本的单例对象。这种写法只是保证了结果的唯一,但是过程中依然会被实例化多次,假如创建对象的频率增大,就意味着内存分配的开销也随之增大。

上面介绍了这么多种写法,看起来每种写法似乎都存在问题,难道就没有一种最优雅、安全、高效的方法吗?这就是我们最后要介绍的枚举式单例,不过在介绍枚举式单例之前,我们先刷一下副本,看看其它写法。

8. 注册式单例

将每一个实例都保存起来,然后在需要使用的时候直接通过唯一的标识获取实例,这便是注册式单例。

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContainerSingleton {
    private ContainerSingleton(){

    }

    private static Map<String,Object> ioc = new ConcurrentHashMap<>();

    public static Object getBean(String className){
        synchronized (ioc){
            //如果容器中不存在当前对象
            if(!ioc.containsKey(className)){
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    //将className作为唯一标识存入容器
                    ioc.put(className,obj);
                }catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            }
        }
        //如果容器中已经存在了单例对象,则直接返回
        return ioc.get(className);
    }
}

新建一个空对象 MyObject.java,用来测试单例。

package singleton.register;

public class MyObject {
}

新建一个测试类 TestContainerSingleton.java

package singleton.register;

public class TestContainerSingleton {
    public static void main(String[] args) {
        MyObject myObject1 = (MyObject) ContainerSingleton.getBean("singleton.register.MyObject");
        MyObject myObject2 = (MyObject) ContainerSingleton.getBean("singleton.register.MyObject");

        System.out.println(myObject1 == myObject2);//输出:true
    }
}

上面返回 true 是因为我们加了 synchronized 关键字,实际上 Spring 框架中用的就是容器式单例,默认是线程不安全的。

9. 单线程安全单例

基于ThreadLocal实现,该单例不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,在单线程环境下线程天生安全。

import java.util.concurrent.ThreadFactory;
public class ThreadLocalSingleton {
    private ThreadLocalSingleton(){

    }

    private static final ThreadLocal<ThreadLocalSingleton> singleton = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };
    
    public static ThreadLocalSingleton getInstance(){
        return singleton.get();
    }
}

测试类:

public class TestThreadLocalSingleton {
    public static void main(String[] args) {
        System.out.println(ThreadLocalSingleton.getInstance());//主线程输出
        System.out.println(ThreadLocalSingleton.getInstance());//主线程输出

        Thread t1 = new Thread(()->{
           ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + singleton);
        });
        t1.start();
    }
}

请在此添加图片描述

从上图可以看到,main 线程输出的和 t1 线程输出的并不是同一个对象,故而 ThreadLocal 式示例仅对单线程是安全的。

10. 枚举式单例

枚举式单例充分利用了枚举类的特性来创建单例对象,目前来说这是最优雅的一种写法。

照例我们新建一个空的对象 MyObject.java 来测试单例。

package singleton.meiju;

public class MyObject {
}
public class EnumSingleton {
    INSTANCE;
    private MyObject myObject;

    EnumSingleton(){
        this.myObject = new MyObject();
    }

    public Object getData() {
        return myObject;
    }

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}
  • 新建测试类 TestEnumSingleton.java 进行测试。
package singleton.meiju;

public class TestEnumSingleton {
    public static void main(String[] args) throws Exception{
        EnumSingleton enumSingleton = EnumSingleton.getInstance();
        System.out.println(enumSingleton.getData() == enumSingleton.getData());//输出:true
    }
}

输出结果为 true,枚举式单例写法能有效的防止通过反射以及序列化手段的破坏,确实为目前最佳的单例实践之选。

小结

尽管实现单例模式的具体思想和方法多种多样,也各有千秋和不足,但在实际的使用中,并不是最优的就是最合适的,在使用单例模式时,应该结合具体的项目需求以及场景来选择合适的实现方式。比如小项目追求线程安全又拥有足够空间的情况下使用饿汉式单例又何尝不可?

致谢&引用:

  • Java Singletons Using Enum
  • Advantages and Disadvantages of using Enum as Singleton in Java
  • 百度百科

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud - 如何本地调试不会注册到线上环境(Nacos)?

问题描述 有时候我们需要本地调试注册到 Nacos 上&#xff0c;但是会影响线上服务的 Feign 请求打到本地导致不通影响了线上业务。 原因分析 一般最传统的解决方案就是修改本地 bootstrap.yml 的 spring.cloud.nacos.discovery.namespace spring:application:name: app-serv…

c语言--枚举类型(声明、使用、优点)

目录 一、枚举类型的声明二、 枚举类型的优点三、 枚举类型的使用 一、枚举类型的声明 枚举顾名思义就是一一列举。 把可能的取值⼀⼀列举。 比如我们现实生活中&#xff1a; ⼀周的星期⼀到星期日是有限的7天&#xff0c;可以⼀⼀列举 性别有&#xff1a;男、女、保密&#x…

WordPress 6.5 “里贾纳”已经发布

WordPress 6.5 “里贾纳”已经发布&#xff0c;其灵感来自著名爵士小提琴家Regina Carter的多才多艺。雷吉娜是一位屡获殊荣的艺术家和著名的爵士乐教育家&#xff0c;以超越流派而闻名&#xff0c;她在古典音乐方面的技术基础和对爵士乐的深刻理解为她赢得了大胆超越小提琴所能…

YUM仓库和编译安装

目录 一.YUM仓库搭建 1.简介&#xff1a; 2.搭建思路&#xff1a; 3.实验&#xff1a;单机yum的创建 二.编译安装 1.简介 2.安装过程 3.实验&#xff1a;编译安装nginx 一.YUM仓库搭建 1.简介&#xff1a; yum是一个基于RPM包&#xff08;是Red-Hat Package Manager红…

STL——vector容器基本使用与常用接口模拟实现

vector的介绍及使用 vector的介绍 vector是表示可变大小数组的序列容器。 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的…

【Apache Doris】周FAQ集锦:第 1 期

【Apache Doris】周FAQ集锦&#xff1a;第 1 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户和…

SD-WAN国际网络专线:高效、合规且可靠的跨境连接解决方案

在数字化时代&#xff0c;企业对跨境网络连接的需求日益增长。SD-WAN技术作为一种新兴的解决方案&#xff0c;正逐渐成为构建跨境网络连接的首选。本文将探讨SD-WAN国际网络专线的发展现状、合规性要求以及选择时需要考虑的关键因素。 SD-WAN技术&#xff1a;跨境网络连接的新…

STM32F407 FSMC并口读取AD7606

先贴一下最终效果图.这个是AD7606并口读取数据一个周期后的数据结果. 原始波形用示波器看是很平滑的. AD7606不知为何就会出现干扰, 我猜测可能是数字信号干扰导致的. 因为干扰的波形很有规律. 这种现象基本上可以排除是程序问题. 应该是干扰或者数字信号干扰,或者是数字和模拟…

MT3017 上色

思路&#xff1a;使用分治&#xff0c;在每个连续区域递归调用heng()和shu() #include <bits/stdc.h> using namespace std; int n, m; int h[5005];int shu(int l, int r) {return r - l 1; } int heng(int l, int r) {int hmin 0x3f3f3f3f;for (int i l; i < r;…

关于C#操作SQLite数据库的一些函数封装

主要功能&#xff1a;增删改查、自定义SQL执行、批量执行&#xff08;事务&#xff09;、防SQL注入、异常处理 1.NuGet中安装System.Data.SQLite 2.SQLiteHelper的封装&#xff1a; using System; using System.Collections.Generic; using System.Data.SQLite; using System.…

EDM邮件推广营销工具多少钱?

云衔科技&#xff0c;凭借专业的技术研发实力与丰富的行业经验&#xff0c;倾力打造了一款智能EDM&#xff08;Electronic Direct Mail&#xff09;邮件营销系统解决方案&#xff0c;以精准、高效、定制化的服务&#xff0c;为企业开启全新的营销之旅。至于价格&#xff0c;云衔…

计算机笔记(3)续20个

41.WWW浏览器和Web服务器都遵循http协议 42.NTSC制式30帧/s 44.三种制式电视&#xff1a;NTSC&#xff0c;PAL&#xff0c;SECAM 45.IP&#xff0c;子网掩码白话文简述&#xff1a; A类地址&#xff1a;取值范围0-127&#xff08;四段数字&#xff08;127.0.0.0&#xff09…

Hadoop和zookeeper集群相关执行脚本(未完,持续更新中~)

1、Hadoop集群查看状态 搭建Hadoop数据集群时&#xff0c;按以下路径操作即可生成脚本 [test_1analysis01 bin]$ pwd /home/test_1/hadoop/bin [test_01analysis01 bin]$ vim jpsall #!/bin/bash for host in analysis01 analysis02 analysis03 do echo $host s…

docker安装jenkins 2024版

docker 指令安装安装 docker run -d --restartalways \ --name jenkins -uroot -p 10340:8080 \ -p 10341:50000 \ -v /home/docker/jenkins:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /usr/bin/docker:/usr/bin/docker jenkins/jenkins:lts访问…

利用Python将TXT文件中的经纬度数据转换为JSON格式

在处理地理空间数据时&#xff0c;经常需要将数据从一种格式转换为另一种格式&#xff0c;以便于后续的分析或可视化。本文将介绍如何使用Python脚本将存储在TXT文件中的经纬度数据转换为JSON格式。 一、背景介绍 经纬度数据是地理信息系统&#xff08;GIS&#xff09;中的基…

关于ansible的模块 ③

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 接《关于Ansible的模块①》和《关于Ansible的模块②》&#xff0c;继续学习ansible的user模块。 user模块可以增、删、改linux远…

BugKu:Simple SSTI

1.进入此题 2.查看源代码 可以知道要传入一个名为flag的参数&#xff0c;又说我们经常设置一个secret_key 3.flask模版注入 /?flag{{config.SECRET_KEY}} 4.学有所思 4.1 什么是flask&#xff1f; flask是用python编写的一个轻量web开发框架 4.2 SSTI成因&#xff08;SST…

[图解]DDD领域驱动设计伪创新-通用语言05

0 00:00:01,060 --> 00:00:04,370 甚至有的人把这个当成恩典 1 00:00:08,730 --> 00:00:11,500 他认为这个对技术人员有好处 2 00:00:13,010 --> 00:00:14,790 他掌握了主动权 3 00:00:15,730 --> 00:00:16,501 这样的话 4 00:00:16,501 --> 00:00:18,430 你…

CANoe自带的TCP/IP协议栈中TCP的keep alive机制是如何工作的

TCP keep alive机制我们已经讲过太多次,车内很多控制器的TCP keep alive机制相信很多开发和测试的人也配置或者测试过。我们今天想知道CANoe软件自带的TCP/IP协议栈中TCP keep alive机制是如何工作的。 首先大家需要知道TCP keep alive的参数有哪些?其实就三个参数:CP_KEEP…

JVM之常用监控工具

JVM之常用监控工具 jps jinfo 获取配置信息 基本语法 jinfo [options] <pid>常用选项 -sysprops&#xff1a;显示JVM进程的系统属性。-flags&#xff1a;显示用于启动JVM的命令行标志和VM选项。-flag <name>&#xff1a;显示指定JVM标志的当前值。-flag [|-]&…