ThreadLocal :在 Java中隱匿的魔法之力

优质博文:IT-BLOG-CN

ThreadLocal 并不是一个Thread,而是 ThreadLocalVariable(线程局部变量)。也许把它命名为 ThreadLocalVar更加合适。线程局部变量就是为每一个使用该变量的线程都提供一个变量值的副本,是 Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。ThreadLocal是除了加锁这种同步方式之外的另一种保证多线程访问出现线程不安全的方式。

从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的,ThreadLocal实例就是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。在 ThreadLocal类中有一个 Map,用于存储每一个线程变量的副本,Map中元素的键为线程对象,而值为对应线程的变量副本。ThreadLocal采用了 “以空间换时间” 的方式。为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

一、API 说明

【1】new ThreadLocal(): 创建一个线程本地变量。
【2】T get(): 返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。
【3】void set(T value): 将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。
【4】void remove(): 移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。
【5】protected T initialValue(): 返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先调用 set(T) 方法再调用 get() 方法,则不会在线程中再调用 initialValue() 方法。若该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。

在程序中一般都重写 initialValue方法,以给定一个特定的初始值。

二、ThreadLocal 简单使用

下面的例子中,开启两个线程,在每个线程内部设置了本地变量的值,然后调用 print方法打印当前本地变量的值。如果在打印之后调用本地变量的 remove方法会删除本地内存中的变量,代码如下所示:

package test;

public class ThreadLocalTest {

    static ThreadLocal<String> localVar = new ThreadLocal<>();

    static void print(String str) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + localVar.get());
        //清除本地内存中的本地变量
        localVar.remove();
    }

    public static void main(String[] args) {
        Thread t1  = new Thread(new Runnable() {
            @Override
            public void run() {
                //设置线程1中本地变量的值
                localVar.set("localVar1");
                //调用打印方法
                print("thread1");
                //打印本地变量
                System.out.println("after remove : " + localVar.get());
            }
        });

        Thread t2  = new Thread(new Runnable() {
            @Override
            public void run() {
                //设置线程1中本地变量的值
                localVar.set("localVar2");
                //调用打印方法
                print("thread2");
                //打印本地变量
                System.out.println("after remove : " + localVar.get());
            }
        });

        t1.start();
        t2.start();
    }
}

下面是运行后的结果:

thread1 :localVar1
after remove : null
thread2 :localVar2
after remove : null

三、ThreadLocal 的实现原理

下面是 ThreadLocal的类图结构,从图中可知:Thread类中有两个变量 threadLocals和 inheritableThreadLocals,二者都是 ThreadLocal内部类 ThreadLocalMap类型的变量,我们通过查看内部内 ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null。

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

只有当线程第一次调用 ThreadLocal的 set或者 get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。除此之外,每个线程的本地变量不是存放在 ThreadLocal实例中,而是放在调用线程的 ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本相当于一个装载本地变量的工具壳,通过 set方法将 value添加到调用线程的 threadLocals中,当调用线程调用 get方法时候能够从它的 threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的 threadLocals中,所以不使用本地变量的时候需要调用 remove方法将 threadLocals中删除不用的本地变量。下面我们通过查看 ThreadLocal的set、get以及remove方法来查看 ThreadLocal具体实怎样工作的。

【1】set方法源码

public void set(T value) {
    //(1)获取当前线程(调用者线程)
    Thread t = Thread.currentThread();
    //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //(3)如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //(4)如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
}

在上面的代码中,(2)处调用 getMap方法获得当前线程对应的 threadLocals(参照上面的图示和文字说明),该方法代码如下:

ThreadLocalMap getMap(Thread t) {
   return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}

如果调用 getMap方法返回值不为 null,就直接将 value值设置到 threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回 null说明是第一次调用 set方法(前面说到过,threadLocals默认值为null,只有调用 set方法的时候才会创建map),这个时候就需要调用 createMap方法创建 threadLocals,该方法如下所示:createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了 threadLocals中。

void createMap(Thread t, T firstValue) {
   t.threadLocals = new ThreadLocalMap(this, firstValue);
}

【2】get方法源码: 在 get方法的实现中,首先获取当前调用者线程,如果当前线程的 threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行 setInitialValue方法初始化 threadLocals变量。在 setInitialValue方法中,类似于 set方法的实现,都是判断当前线程的 threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建 threadLocals变量,同样添加的值为null。

public T get() {
    //(1)获取当前线程
    Thread t = Thread.currentThread();
    //(2)获取当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t);
    //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
    return setInitialValue();
}

private T setInitialValue() {
    //protected T initialValue() {return null;}
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
    return value;
}

【3】remove方法的实现: remove方法判断当前线程对应的 threadLocals变量是否为null,不为 null就直接删除当前线程中指定的 threadLocals变量。

public void remove() {
    //获取当前线程绑定的threadLocals
     ThreadLocalMap m = getMap(Thread.currentThread());
     //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
     if (m != null)
         m.remove(this);
}

【4】如下图所示: 每个线程内部有一个名为 threadLocals的成员变量,该变量的类型为 ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的 key为当前定义的 ThreadLocal变量的 this引用,value为我们使用 set方法设置的值。每个线程的本地变量存放在自己的本地内存变量 threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(可能会导致内存溢出),因此使用完毕需要将其 remove掉。

四、ThreadLocal 不支持继承性

同一个 ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals 中为当前调用线程对应的本地变量,所以二者自然是不能共享的)

package test;

public class ThreadLocalTest2 {

    //(1)创建ThreadLocal变量
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        //在main线程中添加main线程的本地变量
        threadLocal.set("mainVal");
        //新创建一个子线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程中的本地变量值:"+threadLocal.get());
            }
        });
        thread.start();
        //输出main线程中的本地变量值
        System.out.println("mainx线程中的本地变量值:"+threadLocal.get());
    }
}

五、InheritableThreadLocal 类

在上面说到的 ThreadLocal类是不能提供子线程访问父线程的本地变量的,而 InheritableThreadLocal类则可以做到这个功能,下面是该类的源码

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

从上面代码可以看出,InheritableThreadLocal类继承了 ThreadLocal类,并重写了childValue、getMap、createMap三个方法。其中 createMap方法在被调用(当前线程调用 set方法时得到的 map为 null的时候需要调用该方法)的时候,创建的是inheritableThreadLocal而不是 threadLocals。同理,getMap方法在当前调用者线程调用 get方法的时候返回的也不是threadLocals 而是 inheritableThreadLocal。下面我们看看重写的 childValue方法在什么时候执行,怎样让子线程访问父线程的本地变量值。我们首先从 Thread类开始说起

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    //判断名字的合法性
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    //(1)获取当前线程(父线程)
    Thread parent = currentThread();
    //安全校验
    SecurityManager security = System.getSecurityManager();
    if (g == null) { //g:当前线程组
        if (security != null) {
            g = security.getThreadGroup();
        }
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
    g.checkAccess();
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();

    this.group = g; //设置为当前线程组
    this.daemon = parent.isDaemon();//守护线程与否(同父线程)
    this.priority = parent.getPriority();//优先级同父线程
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    //(2)如果父线程的inheritableThreadLocal不为null
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //(3)设置子线程中的inheritableThreadLocals为父线程的inheritableThreadLocals
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    this.stackSize = stackSize;

    tid = nextThreadID();
}

在 init方法中,首先(1)处获取了当前线程(父线程),然后(2)处判断当前父线程的 inheritableThreadLocals是否为null,然后调用createInheritedMap将父线程的 inheritableThreadLocals作为构造函数参数创建了一个新的 ThreadLocalMap变量,然后赋值给子线程。下面是 createInheritedMap方法和 ThreadLocalMap的构造方法

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                //调用重写的方法
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

在构造函数中将父线程的 inheritableThreadLocals成员变量的值赋值到新的 ThreadLocalMap对象中。返回之后赋值给子线程的inheritableThreadLocals。总之,InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中。

六、从 ThreadLocalMap看 ThreadLocal使用不当的内存泄漏问题

【1】基础概念 : 首先我们先看看 ThreadLocalMap的类图,在前面的介绍中,我们知道 ThreadLocal只是一个工具类,他为用户提供get、set、remove接口操作实际存放本地变量的 threadLocals(调用线程的成员变量),也知道 threadLocals是一个ThreadLocalMap类型的变量,下面我们来看看 ThreadLocalMap这个类。在此之前,我们回忆一下 Java中的四种引用类型链接

【2】分析 ThreadLocalMap内部实现: 上面我们知道 ThreadLocalMap内部实际上是一个 Entry数组 private Entry[] table ​,我们先看看 Entry的这个内部类

/**
 * 是继承自WeakReference的一个类,该类中实际存放的key是指向ThreadLocal的弱引用和与之对应的value值(该value值
 * 就是通过ThreadLocal的set方法传递过来的值)由于是弱引用,当get方法返回null的时候意味着回收引用
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** value就是和ThreadLocal绑定的 */
    Object value;

    //k:ThreadLocal的引用,被传递给WeakReference的构造方法
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
//WeakReference构造方法(public class WeakReference<T> extends Reference<T> )
public WeakReference(T referent) {
    super(referent); //referent:ThreadLocal的引用
}

//Reference构造方法
Reference(T referent) {
    this(referent, null);//referent:ThreadLocal的引用
}

Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

在上面的代码中,我们可以看出,当前 ThreadLocal的引用 k被传递给 WeakReference的构造函数,所以 ThreadLocalMap中的key为 ThreadLocal的弱引用。当一个线程调用ThreadLocal的 set方法设置变量的时候,当前线程的 ThreadLocalMap就会存放一个记录,这个记录的 key值为 ThreadLocal的弱引用,value就是通过 set设置的值。如果当前线程一直存在且没有调用该ThreadLocal的 remove方法,如果这个时候别的地方还有对 ThreadLocal的引用,那么当前线程中的 ThreadLocalMap中会存在对ThreadLocal变量的引用和 value对象的引用,是不会释放的,就会造成内存泄漏。

考虑这个 ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的 ThreadLocalMap里面的 key是弱引用,所以当前线程的 ThreadLocalMap里面的 ThreadLocal变量的弱引用在 gc的时候就被回收,但是对应的 value还是存在的这就可能造成内存泄漏(因为这个时候 ThreadLocalMap会存在key为 null但是 value不为 null的 entry项 )。

ThreadLocalMap 中的 Entry的 key使用的是 ThreadLocal对象的弱引用,在没有其他地方对 ThreadLoca依赖,ThreadLocalMap中的 ThreadLocal对象就会被回收掉,但是对应的 value不会被回收,这个时候 Map中就可能存在 key为null但是 value不为null的项,这需要实际使用的时候使用完毕及时调用 remove方法避免内存泄漏。

如果使用线程池,由于线程可能并不是真正的关闭(比如newFixedThreadPool会保持线程一只存活)。因此,如果将一些大对象存放到 ThreadLocalMap中,可能会造成内存泄漏。因为线程没有关闭,无法回收,但是这些对象不会再被使用了。如果希望及时回收对象,则可以使用Thread.remove()方法将变量移除。

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

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

相关文章

SpringAMQP创建交换机和队列

SpringAMQP提供的Exchange接口 一基于bean注解: 一.Fanout交换机 package com.itheima.consumer.config;import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchang…

【MySQL 系列】MySQL 架构篇

在我们开始了解 MySQL 核心功能之前&#xff0c;首先我们需要站在一个全局的视角&#xff0c;来看 SQL 是如何运作执行的。通过这种方式&#xff0c;我们可以在头脑中构建出一幅 MySQL 各组件之间的协同工作方式&#xff0c;有助于我们加深对 MySQL 服务器的理解。 文章目录 1、…

【洛谷 P8662】[蓝桥杯 2018 省 AB] 全球变暖 题解(深度优先搜索+位集合)

[蓝桥杯 2018 省 AB] 全球变暖 题目描述 你有一张某海域 N N N \times N NN 像素的照片&#xff0c;. 表示海洋、 # 表示陆地&#xff0c;如下所示&#xff1a; ....... .##.... .##.... ....##. ..####. ...###. .......其中 “上下左右” 四个方向上连在一起的一片陆地组…

【牛客】VL74 异步复位同步释放

描述 题目描述&#xff1a; 请使用异步复位同步释放来将输入数据a存储到寄存器中&#xff0c;并画图说明异步复位同步释放的机制原理 信号示意图&#xff1a; clk为时钟 rst_n为低电平复位 d信号输入 dout信号输出 波形示意图&#xff1a; 输入描述&#xff1a; clk为时…

Springboot整合rabbitmq(二)

spring boot&#xff0c;为了简化rabbitMQ的使用&#xff0c;它在这里面给咱们提供了一个模板对象。 这个模板对象是什么&#xff1f; 这个模板对象叫RabbitTemplate对象。说白了其实是用来简化rabbitMQ的操作 也就是说之后我们可以通过这个模板对象直接去向rabbitMQ中发送消息…

libevent源码解析:信号事件(二)

文章目录 前言一、用例二、基本数据结构介绍三、源码分析event_base_newevsignal_newevent_addevent_base_dispatch 总结 前言 libevent中对三类事件进行了封装&#xff0c;io事件、信号事件、定时器事件&#xff0c;libevent源码分析系列文章会分别分析这三类事件&#xff0c…

【数据结构与算法】深入浅出:单链表的实现和应用

&#x1f331;博客主页&#xff1a;青竹雾色间. &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ✨人生如寄&#xff0c;多忧何为 ✨ 目录 前言 单链表的基本概念 节点 头节点 尾节点 单链表的基本操作 创建单链表 头插法&#xff1a; 尾插法&#…

测试一下 Anthropic 宣称超过 GPT-4 的 Claude 3 Opus

测试一下 Anthropic 宣称超过 GPT-4 的 Claude 3 Opus 0. 引言1. 测试 Claude 3 Opus 0. 引言 今天测试一下 Anthropic 发布的 Claude 3 Opus。 3月4日&#xff0c;Anthropic 宣布推出 Claude 3 型号系列&#xff0c;该系列在广泛的认知任务中树立了新的行业基准。该系列包括…

Koa: 打造高效、灵活的Node.js后端 (介绍与环境部署)

在上一篇文章中&#xff0c;我们了解了Node.js的基础知识&#xff0c;今天我们将进一步学习Node.js 较新的一个轻量级Web框架Koa&#xff0c;一起创建NodeJS后端服务器吧&#xff01; 一、介绍 Koa是一个新生代Node.js Web框架&#xff0c;由Express原团队成员开发&#xff0c…

redis最新版本在Windows系统上的安装

一、说明 这次安装操作主要是根据redis官网说明&#xff0c;一步步安装下来的&#xff0c;英语比较好的同学&#xff0c;可以直接看文章底部的超链接1&#xff0c;跳到官网按步操作即可。 目前redis的最新稳定版本为redis7.2。 二、Windows环境改造 Redis在Windows上不被官方…

Django高级之-cookie-session-token

Django高级之-cookie-session-token 发展史 1、很久很久以前&#xff0c;Web 基本上就是文档的浏览而已&#xff0c; 既然是浏览&#xff0c;作为服务器&#xff0c; 不需要记录谁在某一段时间里都浏览了什么文档&#xff0c;每次请求都是一个新的HTTP协议&#xff0c; 就是请…

pytorch(四、五)用pytorch实现线性回归和逻辑斯蒂回归(分类)

文章目录 线性回归代码过程准备数据设计模型设计构造函数与优化器训练过程训练代码和结果pytorch中的Linear层的底层原理&#xff08;个人喜欢&#xff0c;不用看&#xff09;普通矩阵乘法实现Linear层实现 回调机制 逻辑斯蒂回归模型损失函数代码和结果 线性回归 代码过程 训…

【Python】成功解决TypeError: ‘tuple‘ object does not support item assignment

【Python】成功解决TypeError: ‘tuple’ object does not support item assignment &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&am…

wps没保存关闭了怎么恢复数据?数据恢复这样做

WPS文件已成为我们不可或缺的一部分。从撰写报告、制作表格到展示演讲&#xff0c;WPS系列软件为我们提供了极大的便利。然而正如任何电子设备都可能遇到的问题一样&#xff0c;WPS文件有时也可能出现损坏的情况&#xff0c;这无疑给我们的工作带来了不小的困扰。 那么当WPS文件…

Manz高压清洗机S11-028GCH-High Quality Cleaner 操作使用说明492页

Manz高压清洗机S11-028GCH-High Quality Cleaner 操作使用说明492页

基于php的用户登录实现(v1版)(持续迭代)

目录 版本说明 数据库连接 登录页面&#xff1a;login.html 登录处理实现&#xff1a;login.php 用户欢迎页面&#xff1a;welcome.php 用户注册页面&#xff1a;register.html 注册执行&#xff1a;DoRegister.php 版本说明 v1实现功能&#xff1a; 数据库连接&#x…

基于UDP实现的网络聊天室

服务器&#xff1a; #include <myhead.h> struct msg {char type;char name[20];char text[1024]; };int main(int argc, const char *argv[]) {if(argc!3){printf("input error\n");printf("./a.out IP地址 端口号\n");return -1;}//1、创建用于通…

美国国家安全局(NSA)和美国政府将Delphi/Object Pascal列为推荐政府机构和企业使用的内存安全编程语言

上周&#xff0c;美国政府发布了《回到构建块&#xff1a;通往安全和可衡量软件的道路》的报告。本报告是美国网络安全战略的一部分&#xff0c;重点关注多个领域&#xff0c;包括内存安全漏洞和质量指标。 许多在线杂志都对这份报告发表了评论&#xff0c;这些杂志强调了对 C…

css clip-path polygon属性实现直角梯形

2024.3.8今天我学习了如何用css实现直角梯形的效果&#xff0c; 效果&#xff1a; 具体实现原理&#xff1a; 一、需要三个div&#xff1a; 外面一个大的div&#xff0c;里面左右两个小的div 我们需要先把第一个div变成直角梯形&#xff1a; 大概是这样&#xff0c;设置好之…

web服务之虚拟主机功能

华子目录 概述基于IP地址的虚拟原理实验 基于不同端口号的虚拟主机原理实验 基于域名的虚拟主机原理域名解析实验 概述 如果每台运行 Linux 系统的服务器上只能运行一个网站&#xff0c;那么人气低、流量小的草根站长就要被迫承担着高昂的服务器租赁费用了&#xff0c;这显然也…