JUC ThreadLocal

news2024/9/25 13:15:59

文章目录

  • ThreadLocal ^1.2^ 的作用
  • 使用场景
  • 示例1
  • ThreadLocal 变量初始化
  • ThreadLocal 源码分析
    • 源码分析总结
  • 内存泄漏问题
    • 示例说明
      • new Thread 方式 执行结果
      • pool 方式执行结果
      • 原因解析
      • 总结

ThreadLocal 1.2 的作用

ThreadLocal 为每个线程提供单独的变量副本。每个变量副本都是线程私有的,线程之间互不影响。当线程执行完成后,对应的变量副本也会被垃圾回收。

在线程中通过 ThreadLocal 的 get、set 方法获取或设置变量的值

使用场景

  1. 每个线程需要有自己单独的变量
  2. 变量需要在多个方法中共享,但不希望被多线程共享

示例1

还是用我们的 Car 对象来说明,比如同一个品牌同一个型号的汽车,可能只有颜色不一样,这时我们就可以使用 ThreadLocal 来定义颜色字段,使他在不同的线程中有不同的副本

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ThreadLocalTest1 {

    public static void main(String[] args) {
        // 线程间共享的对象
        Car car = new Car();

        new Thread(()->{

            car.setSkeleton("骨架1");
            car.setEngine("引擎1");
            car.setTire("轮胎1");
            car.setColor("颜色1");

            log.debug("{}",car);

        },"t1").start();

        new Thread(()->{

            car.setSkeleton("骨架2");
            car.setEngine("引擎2");
            car.setTire("轮胎2");
            car.setColor("颜色2");

            log.debug("{}",car);

        },"t2").start();

        new Thread(()->{

            car.setSkeleton("骨架3");
            car.setEngine("引擎3");
            car.setTire("轮胎3");
            car.setColor("颜色3");

            log.debug("{}",car);

        },"t3").start();

    }

    @Slf4j
    @Getter
    @Setter
    @ToString
    static class Car{

        /**
         * 骨架
         */
        private String skeleton;

        /**
         * 发动机
         */
        private String engine;

        /**
         * 轮胎
         */
        private String tire;

        /**
         * 颜色
         */
        private ThreadLocal<String> color = new ThreadLocal<>();

        public void setColor(String color) {
            this.color.set(color);
        }

        public String getColor() {
            return this.color.get();
        }
    }

}

执行结果:

12:01:14.655 [t1] DEBUG com.yyoo.thread.ThreadLocalTest1 - ThreadLocalTest1.Car(skeleton=骨架3, engine=引擎3, tire=轮胎3, color=颜色1)
12:01:14.655 [t2] DEBUG com.yyoo.thread.ThreadLocalTest1 - ThreadLocalTest1.Car(skeleton=骨架3, engine=引擎3, tire=轮胎3, color=颜色2)
12:01:14.655 [t3] DEBUG com.yyoo.thread.ThreadLocalTest1 - ThreadLocalTest1.Car(skeleton=骨架3, engine=引擎3, tire=轮胎3, color=颜色3)

通过结果可以发现,没有使用 ThreadLocal 的字段每个线程是共享使用的,但color 字段是每个线程独有的

注:这里我们还是使用的 new Thread ,而没有使用线程池,因为使用线程池有一个问题,我们下面再说。

ThreadLocal 变量初始化

我们上面的示例,直接使用的 new ThreadLocal<>(),其初始化的值为:null。有些时候我们需要初始化为其他值

  1. 用 set 方法直接设置
  2. 继承 ThreadLocal ,重写 protected T initialValue() 方法

在第一次调用 get 方法时,会调用此方法返回初始值。(前提是没有调用 set 方法)

  1. 调用 ThreadLocal 的静态方法 withInitial(Supplier<? extends S> supplier)

该方法返回一个 ThreadLocal 的静态内部类 SuppliedThreadLocal,其最终方式就是继承 ThreadLocal ,重写 initialValue 方法。

    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        private final Supplier<? extends T> supplier;

        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }

此处建议使用 withInitial 方法来初始化 ThreadLocal 变量的值

注:如果不初始化 ThreadLocal 变量的值,那么他默认就是 ThreadLocal 的 initialValue 方法返回的值(也就是 null),这样可能引发NPE(空指针异常)。

ThreadLocal 源码分析

我们先从 get 方法入手

public T get() {
	// 获取当前线程对象
    Thread t = Thread.currentThread();
    // 通过当前线程对象获取 ThreadLocalMap 
    // ThreadLocalMap 对象是 ThreadLocal 的一个静态内部类,其实现和 Map 差不多 
    ThreadLocalMap map = getMap(t);
    if (map != null) { // 如果 map 不为空,说明当前线程已经有副本值了
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {// 如果当前副本值不为空,则返回该值
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果当前副本值为空,则执行初始化方法 setInitialValue
    return setInitialValue();
}

setInitialValue 方法逻辑

private T setInitialValue() {
	// 执行 initialValue 方法,获取初始值
    T value = initialValue();
    Thread t = Thread.currentThread();
    // 获取当前线程的 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);// 如果 ThreadLocalMap 为空,则执行 createMap 创建该对象
    return value;
}

以上涉及到两个 ThreadLocalMap 的操作,getMap 和 createMap

    ThreadLocalMap getMap(Thread t) {
    	// 直接返回 t 线程的 threadLocals 属性
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
    	// 设置 t 线程的 threadLocals 属性为 new ThreadLocalMap
    	// ThreadLocalMap 的 key 值为 this(当前 ThreadLocal 对象)
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

在 Thread 类中对应的 threadLocals 字段定义如下:

 ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal set 方法逻辑

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

threadLocals 在 Thread 初始化时,是为空的,在某个 ThreadLocal 对象第一次调用 get 或 set 方法时,创建并赋值的。

源码分析总结

在这里插入图片描述

内存泄漏问题

在此,请大家先回顾一下我们在 JVM 专栏《JVM GC 垃圾收集器》一章中提到的几种引用的类型(强软弱虚),以及它们在 GC 时的特点,这样有助于理解该问题。

其实 ThreadLocal 在我们上面的示例中的引用关系,除了在线程中(我们上面总结的引用关系图),还有就是在我们的 car 对象中,所以最终我们的引用图是这样的

在这里插入图片描述

来说明一下:

  • threadLocals:是 Thread 对象中的字段强引用,类型为:ThreadLocal.ThreadLocalMap,其中存放了 ThreadLocal.ThreadLocalMap.Entry
  • color:是我们自定义对象 Car 中的字段强引用,类型为:ThreadLocal,它用作 ThreadLocal.ThreadLocalMap.Entry 的 Key

示例说明

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ThreadLocalTest2 {


    public static void main(String[] args) throws InterruptedException {

        /*
        核心线程为:2
        最大线程数为:2
        救急线程最长空闲时间:0 秒
        阻塞队列使用固定容量的 LinkedBlockingQueue ,容量为 10
        线程工厂使用 Spring 提供的 CustomizableThreadFactory ,并定义线程的前缀为:my-thread-pool-
         */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,2,0, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),new CustomizableThreadFactory("my-thread-pool-"));


        // pool 线程池方式
        pool.execute(()->log.debug("{}",new Car()));

        // new Thread 方式
//        new Thread(()->log.debug("{}",new Car())).start();

        // 等待以上线程执行完成(当然我们可以使用 FutureTask 来 get 这里我们只是示例而已,请不要较真)
        TimeUnit.SECONDS.sleep(2);
        System.gc();
        log.debug("GC 方法已执行");
        // new Thread 方式在此处要等待 GC 完成
        // pool 方法不用是因为,我们没有关闭线程池,当前程序不会结束
        TimeUnit.SECONDS.sleep(2);


    }


    @Slf4j
    @Getter
    @Setter
    @ToString
    static class Skeleton{
        /**
         * 颜色
         */
        private String color;

        public Skeleton(String color) {
            this.color = color;
        }


        @Override
        protected void finalize() throws Throwable {

            log.debug("{},被 GC 了,方法对象:{}",this,"Skeleton");

        }
    }

    @Slf4j
    @Getter
    @Setter
    @ToString
    static class Car{

        /**
         * 我们这里给 ThreadLocal 一个初始化值
         */
        private ThreadLocal<Skeleton> skeleton = new ThreadLocal<Skeleton>(){
            @Override
            protected Skeleton initialValue() {
                return new Skeleton("黑色");
            }

            // 重写 ThreadLocal 的 finalize 方法,看看它什么时候被 GC
            @Override
            protected void finalize() throws Throwable {

                log.debug("{},被 GC 了,方法对象:{}",this,"ThreadLocal");

            }
        };

        public Car(){

        }

        public Car(Skeleton skeleton){
            this.setSkeleton(skeleton);
        }

        public void setSkeleton(Skeleton skeleton) {
            this.skeleton.set(skeleton);
        }

        public Skeleton getSkeleton() {
            return this.skeleton.get();
        }

        @Override
        protected void finalize() throws Throwable {

            log.debug("{},被 GC 了,方法对象:{}",this,"Car");

        }
    }

}

new Thread 方式 执行结果

14:59:45.990 [Thread-0] DEBUG com.yyoo.thread.ThreadLocalTest2 - ThreadLocalTest2.Car(skeleton=ThreadLocalTest2.Skeleton(color=黑色))
14:59:48.004 [main] DEBUG com.yyoo.thread.ThreadLocalTest2 - GC 方法已执行
14:59:48.004 [Finalizer] DEBUG com.yyoo.thread.ThreadLocalTest2$Skeleton - ThreadLocalTest2.Skeleton(color=黑色),被 GC,方法对象:Skeleton
14:59:48.004 [Finalizer] DEBUG com.yyoo.thread.ThreadLocalTest2$Car - com.yyoo.thread.ThreadLocalTest2$Car$1@6dfb5a1c,被 GC,方法对象:ThreadLocal
14:59:48.004 [Finalizer] DEBUG com.yyoo.thread.ThreadLocalTest2$Car - ThreadLocalTest2.Car(skeleton=ThreadLocalTest2.Skeleton(color=黑色)),被 GC,方法对象:Car

new Thread 执行后,可以看到 GC 时

  • Car 对象被回收了
  • ThreadLocal 对象本身被回收了
  • ThreadLocal 对应的 value Skeleton 也被回收了。

代码中用到的对象都正常回收完成。

pool 方式执行结果

15:24:08.012 [my-thread-pool-1] DEBUG com.yyoo.thread.ThreadLocalTest2 - ThreadLocalTest2.Car(skeleton=ThreadLocalTest2.Skeleton(color=黑色))
15:24:10.040 [main] DEBUG com.yyoo.thread.ThreadLocalTest2 - GC 方法已执行
15:24:10.040 [Finalizer] DEBUG com.yyoo.thread.ThreadLocalTest2$Car - com.yyoo.thread.ThreadLocalTest2$Car$1@6dfb5a1c,被 GC,方法对象:ThreadLocal
15:24:10.040 [Finalizer] DEBUG com.yyoo.thread.ThreadLocalTest2$Car - ThreadLocalTest2.Car(skeleton=ThreadLocalTest2.Skeleton(color=黑色)),被 GC,方法对象:Car
  • Car 对象被回收了
  • ThreadLocal 对象本身被回收了
  • ThreadLocal 对应的 value Skeleton 没有被回收

原因解析

new Thread 方式正常回收,是因为,线程执行完成后,Thread 对象执行完了,会被回收。而 pool 方式执行完成后,线程会回到线程池,而不是关闭线程,该线程还可能执行其他任务。这意味着这个线程对应的 threadLocals 不会被释放(如果我们没有手动将其赋值为 null 手动释放的话)。我们上面提到了 threadLocals 是强引用,所以它是不会被释放的,threadLocals 没有被释放,那么 ThreadLocal 对象为啥被释放了?它不是 threadLocals ThreadLocal.ThreadLocalMap.Entry 的key 值吗?我们来看一眼 ThreadLocal.ThreadLocalMap.Entry 的源码定义

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** ThreadLocal 对应到 value 值 */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

通过 Entry 对象的定义,我们知道 Entry 对象是一个弱引用对象,其引用的值是 ThreadLocal 对象,而 ThreadLocal 对象是 Entry 的 key 值。弱引用对象在每次 GC 时,都会被回收。所以 GC 之后,ThreadLocal 对象被回收了,Entry 对应的 key 值就为空了。但是,这里的 Object value 是不会被回收的,因为通过 threadLocals ThreadLocal.ThreadLocalMap,还是能引用过来,导致 value 值还存在。

总结

所以,在使用线程池的情况下使用 ThreadLocal 我们需要按照如下方式来使用

pool.execute(() -> {
    ThreadLocal<String> tl = null;
    try {
        tl = ThreadLocal.withInitial(() -> "测试");
    }finally {
        if(tl != null){
        	// 通过 remove 方法清除 ThreadLocal 对象的局部变量值
        	// 调用 remove 过后,如果再次 get,会再次重新执行 initialValue 方法,重新初始化
            tl.remove();
        }
    }
});

所以,我们得将我们的示例程序修改一下:

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ThreadLocalTest2 {


    public static void main(String[] args) throws InterruptedException {

        /*
        核心线程为:2
        最大线程数为:2
        救急线程最长空闲时间:0 秒
        阻塞队列使用固定容量的 LinkedBlockingQueue ,容量为 10
        线程工厂使用 Spring 提供的 CustomizableThreadFactory ,并定义线程的前缀为:my-thread-pool-
         */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,2,0, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),new CustomizableThreadFactory("my-thread-pool-"));


        // pool 线程池方式
        pool.execute(()-> {
            Car car = null;
            try {
                car = new Car();
                log.debug("{}", car);
            }finally {
                if(car != null) {
                    car.remove();
                }
            }
        });

        // new Thread 方式
//        new Thread(()->log.debug("{}",new Car())).start();

        // 等待以上线程执行完成(当然我们可以使用 FutureTask 来 get 这里我们只是示例而已,请不要较真)
        TimeUnit.SECONDS.sleep(2);
        System.gc();
        log.debug("GC 方法已执行");
        // new Thread 方式在此处要等待 GC 完成
        // pool 方法不用是因为,我们没有关闭线程池,当前程序不会结束
        TimeUnit.SECONDS.sleep(2);


    }


    @Slf4j
    @Data
    static class Skeleton{
        /**
         * 颜色
         */
        private String color;

        public Skeleton(String color) {
            this.color = color;
        }


        @Override
        protected void finalize() throws Throwable {

            log.debug("{},被 GC 了,方法对象:{}",this,"Skeleton");

        }
    }

    @Slf4j
    @Data
    static class Car{

        /**
         * 我们这里给 ThreadLocal 一个初始化值
         */
        private ThreadLocal<Skeleton> skeleton = new ThreadLocal<Skeleton>(){
            @Override
            protected Skeleton initialValue() {
                return new Skeleton("黑色");
            }

            // 重写 ThreadLocal 的 finalize 方法,看看它什么时候被 GC
            @Override
            protected void finalize() throws Throwable {

                log.debug("{},被 GC 了,方法对象:{}",this,"ThreadLocal");

            }
        };

        public Car(){

        }

        public Car(Skeleton skeleton){
            this.setSkeleton(skeleton);
        }

        public void setSkeleton(Skeleton skeleton) {
            this.skeleton.set(skeleton);
        }

        public Skeleton getSkeleton() {
            return this.skeleton.get();
        }

        public void remove(){
            this.skeleton.remove();
        }

        @Override
        protected void finalize() throws Throwable {

            log.debug("{},被 GC 了,方法对象:{}",this,"Car");

        }
    }

}

执行结果:

08:29:52.113 [my-thread-pool-1] DEBUG com.yyoo.thread.ThreadLocalTest2 - ThreadLocalTest2.Car(skeleton=ThreadLocalTest2.Skeleton(color=黑色))
08:29:54.133 [main] DEBUG com.yyoo.thread.ThreadLocalTest2 - GC 方法已执行
08:29:54.133 [Finalizer] DEBUG com.yyoo.thread.ThreadLocalTest2$Skeleton - ThreadLocalTest2.Skeleton(color=黑色),被 GC,方法对象:Skeleton
08:29:54.133 [Finalizer] DEBUG com.yyoo.thread.ThreadLocalTest2$Car - com.yyoo.thread.ThreadLocalTest2$Car$1@6dfb5a1c,被 GC,方法对象:ThreadLocal
08:29:54.133 [Finalizer] DEBUG com.yyoo.thread.ThreadLocalTest2$Car - ThreadLocalTest2.Car(skeleton=ThreadLocalTest2.Skeleton(color=黑色)),被 GC,方法对象:Car

很多地方(阿里开发规约中也有)都有这样一个建议:ThreadLocal 对象建议使用 static 修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。

在我们的示例下,如果将 ThreadLocal 对象用 static 修饰,我们即便调用了 remove 方法,value 对象也不会被释放,且 ThreadLocal 对象都不会被释放了。 此处请大家自行判断吧,或许此处有我没有理解到的地方,请大家指正,但我依照我们的示例代码结果来总结,虽然用 static 修饰,在分配时只需要一块存储空间,但它无法释放已分配的空间,也可能导致内存泄漏。

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

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

相关文章

史上最全嵌入式(学习路线、应用开发、驱动开发、推荐书籍、软硬件基础)

废话不多说直接上思维导图&#xff01; 如果有觉得图片看不清楚的&#xff0c;有疑问的&#xff0c;可在评论区进行留言&#xff01; 群号&#xff1a; 228447240 嵌入式总括 嵌入式书籍推荐 嵌入式软件知识 嵌入式硬件知识 嵌入式应用开发 嵌入式驱动开发 嵌入式视频推荐: 韦…

WebSocket相关问题

1.WebSocket是什么&#xff1f;和HTTP的区别&#xff1f; WebSocket是一种基于TCP连接的全双工通信协议&#xff0c;客户端和服务器仅需要一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c;并且支持双向数据的传输。WebSocket和HTTP都是基于TCP的应用层协议&am…

【PyTorch][chapter 15][李宏毅深度学习][Neighbor Embedding-LLE]

前言&#xff1a; 前面讲的都是线性降维&#xff0c;本篇主要讨论一下非线性降维. 流形学习&#xff08;mainfold learning&#xff09;是一类借鉴了拓扑流行概念的降维方法. 如上图,欧式距离上面 A 点跟C点更近&#xff0c;距离B 点较远 但是从图形拓扑结构来看&#xff0c; …

书生·浦语大模型全链路开源体系

1&#xff0c;简述大模型的定义与特点&#xff1a; 大模型是指参数数量大于10亿的模型&#xff0c;它的特点包括&#xff1a;模型规模大&#xff0c;数据规模大&#xff0c;计算规模大和任务数量 2. 分析大模型成为通用人工智能的重要途径的原因&#xff1a; 大模型能够从大…

2023年的技术变革,我不是破坏大环境的人

文章目录 前言2023年的技术变革人工智能的崛起元宇宙的跌落物联网的渗入 技术变革的背后技术变革的影响积极的影响负面的影响 技术变革带来的思考 前言 2023无疑是一个充满变革和创新的一年&#xff0c;这背后离不开技术的发展和进步。不论是人工智能的崛起&#xff0c;还是元…

[word] word表格内容自动编号 #经验分享#微信#其他

word表格内容自动编号 在表格中的内容怎么样自动编号&#xff1f;我们都知道Word表格和Excel表格有所不同&#xff0c;Excel表格可以轻松自动编号&#xff0c;那么在Word表格中如何自动编号呢&#xff1f; 1、选中内容后&#xff0c;点击段落-自动编号&#xff0c;选择其中一…

数据结构——C/栈和队列

&#x1f308;个人主页&#xff1a;慢了半拍 &#x1f525; 创作专栏&#xff1a;《史上最强算法分析》 | 《无味生》 |《史上最强C语言讲解》 | 《史上最强C练习解析》 &#x1f3c6;我的格言&#xff1a;一切只是时间问题。 ​ 1.栈 1.1栈的概念及结构 栈&#xff1a;一种特…

计算机缺失concrt140.dll怎么修复?分享5种有效的修复方法

在计算机系统运行过程中&#xff0c;如果发现无法找到“concrt140.dll”这个特定的动态链接库文件&#xff0c;可能会引发一系列问题和故障。首先&#xff0c;我们需要了解“concrt140.dll”是Microsoft Visual Studio中用于实现并行计算框架的重要组件&#xff0c;它的缺失会导…

HarmonyOS 鸿蒙应用开发(十、第三方开源js库移植适配指南)

在前端和nodejs的世界里&#xff0c;有很多开源的js库&#xff0c;通过npm(NodeJS包管理和分发工具)可以安装使用众多的开源软件包。但是由于OpenHarmony开发框架中的API不完全兼容V8运行时的Build-In API&#xff0c;因此三方js库大都需要适配下才能用。 移植前准备 建议在适…

RabbitMQ的延迟队列实现[死信队列](笔记二)

上一篇已经讲述了实现死信队列的rabbitMQ服务配置&#xff0c;可以点击: RabbitMQ的延迟队列实现(笔记一) 目录 搭建一个新的springboot项目模仿订单延迟支付过期操作启动项目进行测试 搭建一个新的springboot项目 1.相关核心依赖如下 <dependency><groupId>org.…

Linux | 进度条 | Linux简单小程序 | 超级简单 | 这一篇就够了

进度条—实例示范 在学习了基本的Linux指令&#xff0c;Linux上vim编译器等等之后&#xff0c;我们就来学习写代码喽~ 今天就给大家详细讲解一下进度条的编写&#xff0c;需要的效果如下图&#xff1a; 进度条—必备知识 回车和换行 在我们学习编程语言中&#xff0c;经常…

【力扣 - 回文链表】

题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 提示&#xff1a; 链表中节点数目在范围[1, 100000] 内 0 < Node.val < 9 方法一&#xff1a;将值复制到数…

jsp教材管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 教材管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

网工内推 | 高级网工,IE认证优先,最高15K,五险一金

01 丰沃创新(北京)科技有限公司 招聘岗位&#xff1a;高级网络工程师 职责描述&#xff1a; 1. 主要负责移动营运商数据中心机房网络的维护工作&#xff1b; 2. 负责防火墙策略调整&#xff0c;负责交换机路由器等网络设备的配置&#xff1b; 3. 负责云专线的入网配置&#…

07:Kubectl 命令详解|K8S资源对象管理|K8S集群管理(重难点)

Kubectl 命令详解&#xff5c;K8S资源对象管理&#xff5c;K8S集群管理 kubectl管理命令kubectl get 查询资源常用的排错命令kubectl run 创建容器 POD原理pod的生命周期 k8s资源对象管理资源文件使用资源文件管理对象Pod资源文件deploy资源文件 集群调度的规则扩容与缩减集群更…

解析spritf和sscanf与模拟常用字符串函数strchr,strtok(二)

今天又来继续我们的字符串函数的文章&#xff0c;这也是最后一篇了。希望这两篇文章能让各位理解透字符串函数。 目录 strchr strtok sprintf和sscanf strchr strchr 是一个用于在字符串中查找特定字符首次出现位置的函数。以下是解析和模拟实现 strchr 函数的示例&…

缓存和分布式锁 笔记

概念 缓存的作用是减低对数据源的访问频率。从而提高我们系统的性能。缓存的流程图 缓存分类 本地缓存 把缓存数据存储在内存中(Map <String,Object>)&#xff0c;其实就是强引用&#xff0c;不会轻易被删除。 分布式缓存 数据冗余&#xff0c;效率不高 整合Redis &l…

计算机设计大赛 深度学习 YOLO 实现车牌识别算法

文章目录 0 前言1 课题介绍2 算法简介2.1网络架构 3 数据准备4 模型训练5 实现效果5.1 图片识别效果5.2视频识别效果 6 部分关键代码7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于yolov5的深度学习车牌识别系统实现 该项目较…

新型RedAlert勒索病毒针对VMWare ESXi服务器

前言 RedAlert勒索病毒又称为N13V勒索病毒&#xff0c;是一款2022年新型的勒索病毒&#xff0c;最早于2022年7月被首次曝光&#xff0c;主要针对Windows和Linux VMWare ESXi服务器进行加密攻击&#xff0c;到目前为止该勒索病毒黑客组织在其暗网网站上公布了一名受害者&#x…