当前位置: 首页 > news >正文

ThreadLocal原理、结构、源码解析

文章目录

    • 一、Thread简介
      • 1.什么是ThreadLocal
      • 2.为什么要是用ThreadLocal
        • 2.1Synchronized、Lock保证线程安全
        • 2.2ThreadLocal保证线程安全
      • 3.ThreadLocal和Synchronized的区别
    • 二、ThreadLocal原理
      • 1.Thread抽象内部结构
      • 2.ThreadLocal源码
        • 2.1Thread、ThreadLocal、ThreadLocalMap、Entry之间关系
        • 2.2ThreadLocal类的set()方法
        • 2.3ThreadLocal类的get()方法
        • 2.4ThreadLocal类的remove()方法
        • 2.5面试:说一说ThreadLocal原理、Thread如何实现线程隔离的
    • 三、深究ThreadLocal
      • 1.为什么不直接用线程id作为ThreadLocalMap的key呢?
      • 2.ThreadLocal导致内存泄漏的原因
      • 3.ThreadLocalMap在设计中有没有考虑到内存泄漏这点呢?
      • 4.在使用ThreadLocal中,我们应该注意什么
      • 5.key是弱引用,GC回收会影响ThreadLocal的正常工作嘛
      • 6.Entry的key为什么要设计成弱引用呢,为什么不使用强引用
        • 6.1Entry的key如果使用强引用
        • 6.2为什么要设计成弱引用
        • 6.3四种引用类型
      • 7.我们希望父子线程之间共享数据,应该怎么做呢
        • 7.1InheritableThreadLocal
        • 7.2InheritableThreadLocal是如何实现父子线程之间共享的

一、Thread简介

1.什么是ThreadLocal

ThreadLocal,即线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,保证了线程安全

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  • 每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题

2.为什么要是用ThreadLocal

保证线程安全,并发场景下,会出现多个线程共享一个变量的场景,这种场景可能会出现线程安全性问题,我们可以采取加锁的方式(Synchronized、Lock)方式,也可以使用使用ThreadLocal方式来避免线程安全问题。

2.1Synchronized、Lock保证线程安全

采用加锁方式保证线程安全到导致系统变慢。共享变量某个时刻只能由一个线程访问,其他线程需要等到该线程释放锁才能访问,影响系统性能。


2.2ThreadLocal保证线程安全

使用ThreadLocal。使用ThreadLocal类访问共享变量时,会在每个线程的本地,都保存一份共享变量的拷贝副本。多线程对共享变量修改时,实际上操作的是这个变量副本,从而保证线性安全。

3.ThreadLocal和Synchronized的区别

  • ThreadLocal和Synchronized都是为了解决并发问题。
  • Synchronized用于线程共享, 而ThreadLocal用于线程隔离
  • Synchronized是时间换空间,ThreadLocal是空间换时间
  • Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

二、ThreadLocal原理

1.Thread抽象内部结构

简单理解(看不懂先往下看再回顾):

  • Thread类中有ThreadLocal.ThreadLocalMap threadLocals属性
  • ThreadLocalMap 是Thread的静态内部类,它维护着Entry对象数组,每个Entry代表一个ThreadLocalMap对象
  • Entry是ThreadLocalMap的静态内部类,存储采用的是k-v形式,key为ThreadLocal,value为我



2.ThreadLocal源码

2.1Thread、ThreadLocal、ThreadLocalMap、Entry之间关系

先根据源码砍砍他们之间的关系

  • Thread的成员变量ThreadLocal.ThreadLocalMap
  • ThreadLocal.ThreadLocalMap是ThreadLocal的静态内部类
  • Entry是ThreadLocalMap的静态内部类,Entry继承了弱引用,也就是说,如果外部没有强引用关联的话,下一次GC时会被回收。
  • 在Entry是一个k-v形式,内部使用ThreadLocal作为key,使用我们设置的value作为value。
  • Entry[] table为ThreadLocal的属性,由ThreadLocalMap维护
//Thread类
class Thread implements Runnable {//ThreadLocalMap是Thread的成员变量ThreadLocal.ThreadLocalMap threadLocals = null;//ThreadLocal类       
public class ThreadLocal<T> {//Entry数组是ThreadLocal的成员变量,该数组由ThreadLocalMap维护private Entry[] table;//静态内部类ThreadLocalMap
static class ThreadLocalMap {//Entry是ThreadLocalMap的静态内部类,注意,Entry继承了弱引用(如果没有被强引用关联,下一次GC会被回收)static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
}
}

2.2ThreadLocal类的set()方法

通过源码可以看到,ThreadLocalMap维护了Entry数组,Entry是k-v形式,key为ThreadLocal,value为我们传入的值,当同一个线程对同一个ThreadLocal进行两次set时,value会被覆盖。

  1. 获取当前线程,根据当前线程获取ThreadLocalMap
  2. 判断ThreadLocalMap是否存在
  3. 存在则将ThreadLocal作为key,传入的值为value,存入ThreadLocalMap的Entry中
  4. 不存在则根据ThreadLocalMap构造函数创建ThreadLocalMap并将ThreadLocal作为key,传入的值为value,存入ThreadLocalMap的Entry中
    public void set(T value) {Thread t = Thread.currentThread();//获取当前线程tThreadLocalMap map = getMap(t);//根据当前线程获取到ThreadLocalMapif (map != null)map.set(this, value);//如果map不为空,则将当前对象ThreadLocal作为key,传入的值为value存入ThreadLocalMap中elsecreateMap(t, value);//如果map为空,则创建ThreadLocalMap对象后,再将k-v存入ThreadLocalMap中}//该方法位于ThreadLocal中ThreadLocalMap getMap(Thread t) {return t.threadLocals;//根据线程t获取Thrad的ThreadLocalMap}//该方法位于ThreadLocal中void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);//调用ThreadLocalMap构造函数,this表示当前类ThreadLocal}//该方法位于ThreadLocal中,该方法位于ThreadLocal中构造函数维护这Entry数组tableThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}

2.3ThreadLocal类的get()方法

  1. 获取当前线程t
  2. 根据线程t获取ThradLocalMap map
  3. map存在则获取Entry,Entry存在获取value
  4. map不存在,初始化ThradLocalMap并将ThreadLocal作为kay,valeu为null存进ThreadLocalMap中,返回value也就是null。
  5. (调用get()方法时,ThreadLocalMap没有初始化则会初始化并ThreadLocal作为kay,valeu为null存进ThreadLocalMap中)
public T get() {Thread t = Thread.currentThread();//获取当前线程ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);//map不为空,获取Entryif (e != null) {//entry不为空。返回value@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();//map为空,初始化threadLocals成员变量的值,也就是初始化把thread Local为key,value=null塞进entry}private T setInitialValue() {T value = initialValue(); //初始化value的值Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //以当前线程为key,获取threadLocals成员变量,它是一个ThreadLocalMapif (map != null)map.set(this, value);  //K,V设置到ThreadLocalMap中elsecreateMap(t, value); //实例化threadLocals成员变量return value;//返回null}protected T initialValue() {return null;}

2.4ThreadLocal类的remove()方法

  1. 获取当前线程,根据当前线程获取ThreadLocalMap
  2. 如果map部位空,则删除map中指定的的Entry
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());//获取当前线程的ThreadLocalMap变量if (m != null)m.remove(this);//对象不为空,则删除
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}
}

2.5面试:说一说ThreadLocal原理、Thread如何实现线程隔离的

ThreadLocal叫本地线程变量,作用就是当多线程访问共享变量时,起到线程隔离的作用,每个线程都有自己的一个副本,且之间不被共享,这种方式采用的是空间换时间的方式。目的是保证线程安全采用空间换时间的方式保证线程安全。

每个线程Thread都有一个成员变量ThreadLocal.ThreadLocalMap thradLocals。也就是说每个线程都有一个ThreadLocalMap,这个ThreadLocalMap是ThreadLocal的静态内部类,他维护者Entry对象数组,Entry对象存储方式是k-v的形式。k为ThreadLocal,V为我我们set进去的值。

在并发场景下,每个线程在往ThreadLocal里面设置值得时候,实际就是存进自己的thradLocals属性中,以ThreadLocal为key,set进去的值为value。实现线程隔离


三、深究ThreadLocal

1.为什么不直接用线程id作为ThreadLocalMap的key呢?

ThreadLocalMap是Thread的属性,维护着Entry数组,Entry的key是ThradLocal,value为我们set入的值。

如果将线程id作为key,那么当一个Thread有多个ThreadLocal进行set()的时候,无法区分value是哪个ThreadLocal的,或者说无论多少个ThreadLocal,每次set进去,由于key都是ThreadId,会导致每次set都会被覆盖。

如图所示,一个key对应多个ThreadLocal。ThreadLocal-1 set()的时候key为Thread,ThreadLocal-1 set()的时候key依然为Thread

image-20230225181347271

2.ThreadLocal导致内存泄漏的原因

ThreadLocal导致内存泄漏愿意你有两个

  1. 使用完后没有remove()由于ThreadLocalMap 的生命周期跟 Thread 一样长,对于重复利用的线程来说(例如核心线程池中的线程),如果没有手动删除(调用remove()方法),会导致Entry对象越来越多,从而导致内存泄漏.
  2. 第二种原因下面讲解

Entry类继承了弱引用,super(k);使ThreadLocal也是一个弱引用

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

ThreadLocal引用示意图

image-20230225183419928

  • ThreadLocalMap使用ThreadLocal的弱引用作为key,当ThreadLocal变量被手动设置为null,即一个ThreadLocal没有外部强引用来引用它,当系统GC时,ThreadLocal一定会被回收。
  • 这样的话,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value
  • 如果当前线程再迟迟不结束的话(比如线程池的核心线程),这些key为null的Entry的value就会一直存在一条强引用链:Thread变量 -> Thread对象 -> ThreaLocalMap -> Entry -> value -> Object 永远无法回收,造成内存泄漏。

当Thread手动设置为null后的因用链

image-20230225184237427

此时,堆中的ThreadLocal只存在弱引用,再下一次GC时会被回收。回收后,Entry中的key=null,这个Entry就没有办法被访问。导致内存泄漏。


3.ThreadLocalMap在设计中有没有考虑到内存泄漏这点呢?

实际上ThreadLocalMap在设计的时候就考虑到这个情况了,所以ThreadLocal的get、set方法中加了一些防护措施。在执行set、get方法的时候,会清楚线程中ThreadLocalMap中key为null的Entry。

ThreadLocal的set()方法防止内存泄漏措施

  private void set(ThreadLocal<?> key, Object value) {// We don't use a fast path as with get() because it is at// least as common to use set() to create new entries as// it is to replace existing ones, in which case, a fast// path would fail more often than not.Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;//触发一次Log2(N)复杂度的扫描,目的是清除过期Entry  if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}

ThreadLocal的get()方法防止内存泄露措施

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {//去ThreadLocalMap获取Entry,方法里面有key==null的清除逻辑ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;else//其中有key为null的清楚逻辑return getEntryAfterMiss(key, i, e);}private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;while (e != null) {ThreadLocal<?> k = e.get();if (k == key)return e;if (k == null)// Entry的key为null,则表明没有外部引用,且被GC回收,是一个过期EntryexpungeStaleEntry(i);elsei = nextIndex(i, len);e = tab[i];}return null;}

4.在使用ThreadLocal中,我们应该注意什么

  • 将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
  • 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

5.key是弱引用,GC回收会影响ThreadLocal的正常工作嘛

  • 弱引用:具有弱引用的对象拥有更短暂的生命周期。如果一个对象只有弱引用存在了,则下次GC将会回收掉该对象(不管当前内存空间足够与否)

当然不会,因为还有ThreadLocal强引用着它,是不会被GC回收的,除非手动将ThradLocal置为null

image-20230225183419928

验证

package com.jhq.threadLocal;import java.lang.ref.WeakReference;
/*** @BelongsProject: study* @BelongsPackage: com.jhq.threadLocal* @Author: jianghq* @CreateTime: 2023-02-24  17:02* @Description: ThreadLocal的key既然是弱引用.会不会GC贸然把key回收掉,进而影响ThreadLocal的正常使用? 不会* @Version: 1.0*/
public class WeakReferenceTest {public static void main(String[] args) {Object object = new Object();WeakReference<Object> testWeakReference = new WeakReference<>(object);System.out.println("GC回收之前,弱引用:"+testWeakReference.get());//触发系统垃圾回收for(int i=0;i<100;i++){//我们只能建议GC回收,并不能百分之百保证真的回收System.gc();}System.out.println("GC回收之后,弱引用:"+testWeakReference.get());//手动设置为object对象为nullobject=null;System.gc();System.out.println("对象object设置为null,GC回收之后,弱引用:"+testWeakReference.get());}
}
输出:GC回收之前,弱引用:java.lang.Object@cc34f4dGC回收之后,弱引用:java.lang.Object@cc34f4d对象object设置为nullGC回收之后,弱引用:null

image.png

6.Entry的key为什么要设计成弱引用呢,为什么不使用强引用

官方回答

o help deal with very large andlong-lived usages, the hash table entries use WeakReferences for keys.

为了应对非常大和长时间的用途,哈希表使用弱引用的 key。

6.1Entry的key如果使用强引用

先看看引用图

image-20230225185852595

正这种情况,由于ThreadLocalMap生命周期和Thread一样长,使用强引用之后,只要Thrad存在,那么Entry就会一直存在内存中,如果线程为核心线程池中的线程,Entry就会一直存在,导致内存泄漏。

6.2为什么要设计成弱引用

如果Key使用弱引用:ThreadLocal置为null,因为ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value则在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

6.3四种引用类型

  • 强引用:我们new出来的对象就是强引用,例如Object o=new Onject();强引用不会被GC回收。
  • 软引用:一个对象只有软引用时,内存空间足够的情况下不会被回收,当内存不足时会被回收。
  • 弱引用:当对象只有弱引用时,下一次GC时会被回收。
  • 虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

7.我们希望父子线程之间共享数据,应该怎么做呢

7.1InheritableThreadLocal

使用InheritableThreadLocal

public class InheritableThreadLocalTest {public static void main(String[] args) {ThreadLocal threadLocal=new ThreadLocal();InheritableThreadLocal inheritableThreadLocal=new InheritableThreadLocal();threadLocal.set("main线程.ThreadLocal");inheritableThreadLocal.set("main线程.InheritableThreadLocal");new Thread(()->{threadLocal.set(Thread.currentThread().getName()+"ThreadLocal");inheritableThreadLocal.set(Thread.currentThread().getName()+"InheritableThreadLocal");}).start();new Thread(()->{System.out.println("当前线程"+Thread.currentThread().getName()+"===="+threadLocal.get());System.out.println("当前线程"+Thread.currentThread().getName()+"===="+inheritableThreadLocal.get());}).start();}
}//输出
当前线程Thread-0====null
当前线程Thread-0====main线程.InheritableThreadLocal

可以看出,ThreadLocal在父子线程之间是不共享的。InheritableThreadLocal可以在父子线程之间共享。但仅限于父子线程之间。

7.2InheritableThreadLocal是如何实现父子线程之间共享的

InheritableThreadLocal是Thread的成员变量,返回类型也是ThreadLocal.ThreadLocalMap

    /* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

Thread的init方法

    private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {//>.....衡略//如果父线程的inheritableThreadLocals不为空,则将inheritableThreadLocals赋值给子类if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */tid = nextThreadID();}static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);}

相关文章:

ThreadLocal原理、结构、源码解析

文章目录一、Thread简介1.什么是ThreadLocal2.为什么要是用ThreadLocal2.1Synchronized、Lock保证线程安全2.2ThreadLocal保证线程安全3.ThreadLocal和Synchronized的区别二、ThreadLocal原理1.Thread抽象内部结构2.ThreadLocal源码2.1Thread、ThreadLocal、ThreadLocalMap、En…...

分布式之PBFT算法

写在前面 在分布式之拜占庭问题 一文中我们分析了拜占庭问题&#xff0c;并一起看了支持拜占庭容错的口信消息性和签名消息性算法&#xff0c;但是这两种算法都有一个非常严重的问题&#xff0c;就是消息数量太多&#xff0c;通信的成本太大&#xff0c;消息数量复杂度为O(n ^…...

Linux 操作系统——查看/修改系统时区、时间、本地时间修改为UTC

文章目录1.背景描述2.知识储备3.解决步骤1. 查看当前时区2.修改设置Linux服务器时区3.复制相应的时区文件&#xff0c;替换系统时区文件&#xff1b;或者创建链接文件4. 查看和修改Linux的时间5. 硬件时间和系统时间的 相互同步1.背景描述 最近一个项目日期采用java8的LocalDa…...

CSS数据类型以及符号

css数据类型定义的是css属性中具有代表性的值&#xff0c;在规范的语法格式中&#xff0c;使用关键字外加一对 <和>表示&#xff0c;例如数值类型<number>、色值类型<color>等。 举个例子&#xff1a;background-image这个css属性语法结构如下&#xff1a; …...

LeetCode-54. 螺旋矩阵

题目来源 54. 螺旋矩阵 题目思路 while循环只遍历"环"&#xff0c;不成环就不遍历了 四个边界 上边界 top : 0下边界 bottom : matrix.length - 1左边界 left : 0右边界 right : matrix[0].length - 1 矩阵不一定是方阵 top < bottom && left < r…...

【Python入门第十八天】Python For 循环

Python For 循环 for 循环用于迭代序列&#xff08;即列表&#xff0c;元组&#xff0c;字典&#xff0c;集合或字符串&#xff09;。 这与其他编程语言中的 for 关键字不太相似&#xff0c;而是更像其他面向对象编程语言中的迭代器方法。 通过使用 for 循环&#xff0c;我们…...

Qt图片定时滚动播放器

目录参考结构PicturePlay.promain.cpppictureplay.hpictureplay.cpppictureplay.ui效果源码参考 Qt图片浏览器 QT制作一个图片播放器 Qt中自适应的labelpixmap充满窗口后&#xff0c;无法缩小只能放大 可以显示jpg、jpeg、png、bmp。可以从电脑上拖动图到窗口并显示出来或者打开…...

李宏毅2023春季机器学习课程

目录2021&2022课程重磅须知我维护的其他项目更新日志课程地址课程资料直链课程作业直链其他优质课程2021&2022课程 CSDN Github 重磅须知 为方便所有网课资料与优质电子书籍的实时更新维护&#xff0c;创建一个在线实时网盘文件夹&#xff1b;   网盘获取方式&#…...

计算机操作系统知识点汇总

计算机操作系统选择填空题&#xff0c;300知识点&#xff0c;包含操作系统概论、处理机管理、内存管理、设备管理、文件管理等&#xff0c;为大学生期末创造奇迹提供无限可能 1、填空题 1、操作系统是对计算机资源进行管理的软件 2、操作系统是提供了处理机管理、 存储器管理…...

【离线数仓-8-数据仓库开发DWD层设计要点-交易域相关事实表】

离线数仓-8-数据仓库开发DWD层设计要点-交易域相关事实表离线数仓-8-数据仓库开发DWD层设计要点-交易域相关事实表一、DWD层设计要点二、交易域相关事实表1.交易域加购事务事实表1.加购事务事实表 前期梳理2.加购事务事实表 DDL表设计分析3.加购事务事实表 加载数据分析1.首日全…...

计算机网络(七):DNS协议和原理,DNS为什么用UDP,网页解析的全过程

文章目录一、什么是DNS二、DNS的作用三、DNS作用四、DNS为什么用UDP五、如果打开一个网站很慢&#xff0c;要如何排查六、网页解析的全过程一、什么是DNS DNS是域名系统的英文缩写&#xff0c;是一种组织成域层次结构的计算机和网络服务命名系统&#xff0c;用于TCP/IP网络。 …...

算法23:多叉树_派对的最大快乐值

公司的每个员工都符合 Employee 类的描述。整个公司的人员结构可以看作是一棵标准的、 没有环的多叉树。树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。 叶节点是没有任何下属的基层员工(subordinates列表为空)&#xff0c;除基层员工外&#xff0c;每…...

中国ETC行业市场规模及未来发展趋势

中国ETC行业市场规模及未来发展趋势编辑根据市场调研在线网发布的2023-2029年中国ETC行业发展策略分析及战略咨询研究报告分析&#xff1a;随着政府坚持实施绿色出行政策&#xff0c;ETC行业也受到了极大的支持。根据中国智能交通协会统计&#xff0c;2017年中国ETC行业市场规模…...

每日刷题(一)——只出现一次的数字

前言 今天遇到一个位运算的题目&#xff0c;感觉很有意思&#xff0c;记录一下。 Question1 136. 只出现一次的数字 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实…...

洛谷P5737 【深基7.例3】闰年展示 C语言/C++

【深基7.例3】闰年展示 题目描述 输入 x,yx,yx,y&#xff0c;输出 [x,y][x,y][x,y] 区间中闰年个数&#xff0c;并在下一行输出所有闰年年份数字&#xff0c;使用空格隔开。 输入格式 输入两个正整数 x,yx,yx,y&#xff0c;以空格隔开。 输出格式 第一行输出一个正整数&a…...

shell注释

注释对于任何编程语言都是不可忽视的重要组成部分&#xff0c;编写者通过注释来为其他人提供解释或提示&#xff0c;能有效提高代码的可读性。 Bash 同其他编程语言一样提供了两种类型注释的支持。 单行注释多行注释一、Bash 单行注释 在注释段落的开头使用 # &#xff0c;如下…...

【C++入门(上篇)】C++入门学习

前言&#xff1a; 在之前的学习中&#xff0c;我们已经对初阶数据结构进行相应了学习&#xff0c;加上之前C语言的学习功底。今天&#xff0c;我们将会踏上更高一级“台阶”的学习-----即C的学习&#xff01;&#xff01;&#xff01; 文章目录1.C 简介1.1什么是C1.2.C的发展史…...

【密码学】 一篇文章讲透数字签名

【密码学】 一篇文章讲透数字签名 数字签名介绍 数字签名&#xff08;又称公钥数字签名&#xff09;是只有信息的发送者才能产生的别人无法伪造的一段数字串&#xff0c;这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。它是一种类似写在纸上的普通的物理签名…...

POI导入导出、EasyExcel批量导入和分页导出

文件导入导出POI、EasyExcel POI&#xff1a;消耗内存非常大&#xff0c;在线上发生过堆内存溢出OOM&#xff1b;在导出大数据量的记录的时候也会造成堆溢出甚至宕机&#xff0c;如果导入导出数据量小的话还是考虑的&#xff0c;下面简单介绍POI怎么使用 POI导入 首先拿到文…...

手把手教你做微信公众号

手把手教你做微信公众号 微信公众号可以通过注册的方式来建立。 1.进入微信公众平台 首先&#xff0c;在浏览器中搜索微信公众号&#xff0c;网页第一个就是&#xff0c;如下图所示&#xff0c;我们点进去。 2.注册微信平台账号 进入官网之后&#xff0c;如下图所示&#…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral&#xff08;热门工具 Ruff 的开发者&#xff09;推出的下一代高性能 Python 包管理器和构建工具&#xff0c;用 Rust 编写。它旨在解决传统工具&#xff08;如 pip、virtualenv、pip-tools&#xff09;的性能瓶颈&#xff0c;同时…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...

【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验

Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

API网关Kong的鉴权与限流:高并发场景下的核心实践

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中&#xff0c;API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关&#xff0c;Kong凭借其插件化架构…...