码出高效:Java开发手册笔记(java对象四种引用关系及ThreadLocal)
码出高效:Java开发手册笔记(java对象四种引用关系及ThreadLocal)
- 前言
- 一、引用类型
- 二、ThreadLocal价值
- 三、ThreadLocal副作用
前言
“水能载舟,亦能覆舟。”用这句话来形容 ThreadLocal 最贴切不过。ThreadLocal 初衷是在线程并发时,解决变量共享问题,但由于过度设计,比如弱引用和哈希碰撞,导致理解难度大、使用成本高,反而成为故障高发点,容易出现内存泄漏、脏数据、共享对象更新等问题。单从 ThreadLocal 的命名看人们会认为只要用它就对了,包治变量共享问题,然而并不是。本节以内存模型、弱引用、哈希算法为铺垫 , 然后从 CS 真人游戏的示例代码入手,详细分析 ThreadLocal 源码。我们从中可以学习到全新的编程思维方式,并认识到问题的来源,也能够帮助我们谙熟此类的设计之道,扬长避短。
一、引用类型
前面介绍了内存布局和垃圾回收,对象在堆上创建之后所持有的引用其实是一种变量类型,引用之间可以通过赋值构成一条引用链。从 GC Roots 开始遍历,判断引用是否可达。引用的可达性是判断能否被垃圾回收的基本条件。JVM会据此自动管理内存的分配与回收,不需要开发工程师干预。但在某些场景下,即使引用可达,也希望能够根据语义的强弱进行有选择的回收,以保证系统的正常运行。根据引用类型语义的强弱来决定垃圾回收的阶段,我们可以把引用分为强引用、软引用、弱引用和虚引用四类。后三类引用,本质上是可以让开发工程师通过代码方式来决定对象的垃圾回收时机。我们先简要了解一下这四类引用。
强引用,即 Strong Reference , 最为常见。如 Object object = new Object();这样的变量声明和定义就会产生对该对象的强引用。只要对象有强引用指向,并且 GC Roots可达,那么 Java 内存回收时,即使濒临内存耗尽,也不会回收该对象。
软引用 , 即 Soft Reference ,引用力度弱于“强引用”,是用在非必需对象的场景。 在即将 OOM 之前,垃圾回收器会把这些软引用指向的对象加入回收范围,以获得更多的内存空间,让程序能够继续健康运行。主要用来缓存服务器中间计算结果及不需
要实时保存的用户行为等。
弱引用 , 即 Weak Reference , 引用强度较前两者更弱,也是用来描述非必需对象的。如果弱引用指向的对象只存在弱引用这 条线路,则在下一次 YGC 时会被回收。由于 YGC 时间的不确定性,弱引用何时被回收也具有不确定性。弱引用主要用于指
向某个易消失的对象,在强引用断开后,此引用不会劫持对象。调用 WeakReference.get() 可能返回 null ,要注意空指针异常。
虚引用 , 即 Phantom Reference , 是极弱的一种引用关系,定义完成后,就无法通过该引用获取指向的对象。为一个对象设置虚引用的唯一目的就是希望能在这个对象被回收时收到一个系统通知。虚引用必须与引用队列联合使用,当垃圾回收时,如果发现存在虚引用,就会在回收对象内存前,把这个虚引用加入与之关联的引用队列中。
举个具体例子 , 在房产交易市场中 , 某个卖家有一套房子,成功出售给某个买家后引用置为 null。这里有 4 个买家使用 4 种不同的引用关系指向这套房子。买家buyer1 是强引用,如果把 seller 引用赋值给它,则永久有效,系统不会因为 seller=null就触发对这套房子的回收 , 这是房屋交易市场最常见的交付方式。买家 buyer2 是软引用,只要不产生 OOM , buyer2.get() ,就可以获取房子对象 ,就像房子是租来的一样。买家 buyer3 是弱引用 ,一旦过户后 ,seller 置为 null, buyer3 的房子持有时间估计只有几秒钟 , 卖家只是给买家做了一张假的房产证 , 买家高兴了几秒钟后 ,发现房子已经不是自己的了。buyer4 是虚引用 , 定义完成后无法访问到房子对象,卖家只是虚构了房源 ,是空手套白狼的诈骗术。
强引用是最常用的,而虚引用在业务中几乎很难用到。本节重点介绍一下软引用和弱引用。先来说明一下软引用的回收机制。首先设置JVM参数 -Xms20m -Xmx20m , 即只有 20MB 的堆内存空间。在下方的示例代码中不断地往集合里添加House 对象 , 而每个 House 有 2000 个 Door 成员变量 , 狭小的堆空间加上大对象的产生 , 就是为了尽快触达内存耗尽的临界状态 :
二、ThreadLocal价值
我们从真人CS游戏说起。游戏开始时,每个人能够领到一把电子枪,枪把上有
三个数字:子弹数、杀敌数、自己的命数,为其设置的初始值分别为 1500 、 0、 10。假设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢。如果每个线程写死这三个值,万一将初始子弹数统 改成 1000 发呢?如果共享,那么线程之间的并发修改会导致数据不准确。能不能构造这样一个对象,将这个对象设置为共享变量 ,统一设置初始值,但是每个线程对这个值的修改都是互相独立的。这个对象就是ThreadLocal。注意不能将其翻译为线程本地化或本地线程,英语恰当的名称应该叫作:CopyValuelntoEveryThread。具体示例代码如下:
package com.example.demo.test;import java.util.concurrent.ThreadLocalRandom;/*** @Author: Ron* @Create: 2023-04-23 11:01*/
public class CsGameByThreadLocal {private static final Integer BULLET_NUMBER = 1500;private static final Integer KILLED_ENEMIES = 0;private static final Integer LIFE_VALUE = 10;private static final Integer TOTAL_PLAYERS = 10;// 随机数用来展示每个对象的不同的数据(第1处)private static final ThreadLocalRandom RANDOM = ThreadLocalRandom.current();// 初始化子弹数private static final ThreadLocal<Integer> BULLET_NUMBER_THREADLOCAL = new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue() {return BULLET_NUMBER;}};// 初始化击杀敌人数private static final ThreadLocal<Integer> KILLED_ENEMIES_THREADLOCAL = new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue() {return KILLED_ENEMIES;}};// 初始化生命值private static final ThreadLocal<Integer> LIFE_VALUE_THREADLOCAL = new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue() {return LIFE_VALUE;}};// 定义每位队员private static class Player extends Thread {@Overridepublic void run() {Integer bullets = BULLET_NUMBER_THREADLOCAL.get() - RANDOM.nextInt(BULLET_NUMBER);Integer killEnemies = KILLED_ENEMIES_THREADLOCAL.get() + RANDOM.nextInt(TOTAL_PLAYERS / 2);Integer lifeValue = LIFE_VALUE_THREADLOCAL.get() - RANDOM.nextInt(LIFE_VALUE);System.out.println(getName() + ", BULLET_NUMBER is " + bullets);System.out.println(getName() + ", KILLED_ENEMIES is " + killEnemies);System.out.println(getName() + ", LIFE_VALUE is " + lifeValue + "\n");BULLET_NUMBER_THREADLOCAL.remove();KILLED_ENEMIES_THREADLOCAL.remove();LIFE_VALUE_THREADLOCAL.remove();}}public static void main(String[] args) {for (int i = 0; i < TOTAL_PLAYERS; i++) {new Player().start();}}}
此示例中 , 没有进行 set 操作 , 那么初始值又是如何进入每个线程成为独立拷贝的呢?首先 ,虽然 ThreadLocal 在定义时覆写了 initialValue() 方法,但并非是在BULLET_NUMBER_THREADLOCAL 对象加载静态变量的时候执行的 , 而是每个线程在 ThreadLocal.get() 的时候都会执行到 , 其源码如下:
/*** Returns the value in the current thread's copy of this* thread-local variable. If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}
每个线程都有自己的 ThreadLocalMap , 如果 map== null ,则直接执行setlnitialValue()。如果 map 已经创建,就表示 Thread 类的 threadLocals 属性已经初始化;如果e==null , 依然会执行到 setlnitialValue() 。setlnitialValue() 的源码如下 。
/*** Returns the current thread's "initial value" for this* thread-local variable. This method will be invoked the first* time a thread accesses the variable with the {@link #get}* method, unless the thread previously invoked the {@link #set}* method, in which case the {@code initialValue} method will not* be invoked for the thread. Normally, this method is invoked at* most once per thread, but it may be invoked again in case of* subsequent invocations of {@link #remove} followed by {@link #get}.** <p>This implementation simply returns {@code null}; if the* programmer desires thread-local variables to have an initial* value other than {@code null}, {@code ThreadLocal} must be* subclassed, and this method overridden. Typically, an* anonymous inner class will be used.** @return the initial value for this thread-local*/protected T initialValue() {return null;}/*** Variant of set() to establish initialValue. Used instead* of set() in case user has overridden the set() method.** @return the initial value*/private T setInitialValue() {// 这是一个保护方法 ,CsGameByThreadLocal 中初始化 ThreadLocal 对象时已覆写T value = initialValue();Thread t = Thread.currentThread();// getMap 的源码就是提取线程对象 t 的 ThreadLocalMap 属性 : t.threadLocalsThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}
在CsGameByThreadLocal 类的第1处,使用了 ThreadLocalRandom 生成单独的Random 实例。此类在 JDK7 中引入,它使得每个线程都可以有自己的随机数生成器。我们要避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 而导致性能下降。
我们已经知道了 ThreadLocal 是每个线程单独持有的。因为每个线程都有独立的变量副本,其他线程不能访问,所以不存在线程安全问题,也不会影响程序的执行性能。 ThreadLocal 对象通常是由 private static 修饰的,因为都需要复制进入本地线程,所以非 static 作用不大。需要注意的是,Thread Local 无法解决共享对象的更新问题,下面的代码实例将证明这点。因为 CsGameByThreadLocal 中使用的是Integer 的不可变对象 , 所以可以使用相同的编码方式来操作一下可变对象看看,示例源码如下:
package com.example.demo.test;import java.util.concurrent.TimeUnit;/*** @Author: Ron* @Create: 2023-04-23 16:23*/
public class InitValueInThreadLocal {private static final StringBuilder INIT_VALUE = new StringBuilder("init");// 覆写ThreadLocal的initialValue,返回StringBuilder静态引用private static final ThreadLocal<StringBuilder> builder = new ThreadLocal<StringBuilder>() {@Overrideprotected StringBuilder initialValue() {return INIT_VALUE;}};private static class AppendStringThread extends Thread {@Overridepublic void run() {StringBuilder inThread = builder.get();for (int i = 0; i < 10; i++) {inThread.append("-" + i);}System.out.println(inThread.toString());}}public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {new AppendStringThread().start();}TimeUnit.SECONDS.sleep(10);}
}
输出的结果是乱序不可控的,所以使用某个引用来操作共享对象时,依然需要进行线程同步。
我们看看 ThreadLocal 和 Thread 的类图,了解其主要方法,如图 7-9 所示。
ThreadLocal 有个静态内部类叫 ThreadLocalMap ,它还有个静态内部类叫Entry,在 Thread 中的ThreadLocalMap 属性的赋值是在 ThreadLocal 类中的createMap()中进行的。 ThreadLocal 与ThreadLocalMap 有三组对应的方法 get()、set() 和 remove(), 在 ThreadLocal 中对它们只做校验和判断 ,最终的实现会落在ThreadLocalMap 上。 Entry 继承自 WeakReference ,没有方法,只有一个 value 成员变量,它的 key 是 ThreadLocal 对象。再从栈与堆的内存角度看看两者的关系 ,如 图 7-10 所示。
图 7-10 中的简要关系:
- 1 个 Thread 有且仅有 1 个 ThreadLocalMap 对象;
- 1 个 ThreadLocalMap 对象存储多个 Entry 对象;
- 1 个 Entry 对象的 Key 弱引用指向 1 个 ThreadLocal 对象;
- 1 个 ThreadLocal 对象可以被多个线程所共享;
- ThreadLocal 对象不持有 Value, Value 由线程的 Entry 对象持有。
图中的红色虚线箭头是重点,也是整个 ThreadLocal 难以理解的地方, Entry 的源码如下:
/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object). Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table. Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
所有 Entry 对象都被 ThreadLocalMap 类实例化对象 threadLocals 持有。当线程对象执行完毕时 ,线程对象内的实例属性均会被垃圾回收。源码中的红色字标识的 ThreadLocal 的弱引用 ,即使线程正在执行 中, 只要 ThreadLocal 对象 引用被置成null, Entry 的 Key 就会自动在下一次 YGC 时被垃圾回收。而在 ThreadLocal 使用set() 和 get() 时 , 又会自动地将那些 key==null 的 value 置为 null , 使 value 能够被垃圾回收,避免内存泄漏,但是理想很丰满 , 现实很骨感 , ThreadLocal 如源码注释所述:
ThreadLocal instances are typically private static fields in classes.
ThreadLocal 对象通常作为私有静态变量使用 , 那么其生命周期至少不会随着线程结束而结束。
线程使用 ThreadLocal 有三个重要方法 ·
( 1 ) set() :如果没有 set 操作的 ThreadLocal ,容易引起脏数据问题。
( 2 ) get(): 始终没有 get 操作的 ThreadLocal 对象是没有意义的。
( 3 ) remove() :如果没有 remove 操作,容易引起内存泄漏。
如果说一个 ThreadLocal 是非静态的 , 属于某个线程实例类 , 那就失去了线程间共享的本质属性。那么 ThreadLocal 到底有什么作用呢?我们知道 , 局部变量在方法内各个代码块间进行传递 , 而类内变量在类内方法间进行传递。复杂的线程方法可能需要调用很多方法来实现某个功能 ,这时候用什么来传递线程内变量呢?答案就是 ThreadLocal , 它通常用于同一个线程内,跨类、跨方法传递数据。如果没有ThreadLocal ,那么相互之间的信息传递,势必要靠返回值和参数 , 这样无形之中 ,有些类甚至有些框架会互相耦合。通过将 Thread 构造方法的最后一个参数设置为 true ,可以把当前线程的变量继续往下传递给它创建的子线程
最后, SimpleDateFormat 是线程不安全的类,定义为 static 对象,会有数据同步风险。通过源码可以看出, SimpleDateFormat 内部有一个 Calendar 对象,在日期转字符串或字符串转日期的过程中,多线程共享时有非常高的概率产生错误 , 推荐的方式之一就是使用 ThreadLocal,让每个线程单独拥有这个对象。示例代码如下:
package com.example.demo.test;import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @Author: Ron* @Create: 2023-04-24 14:05*/
public class SimpleDateFormatDemo {public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");public static final ThreadLocal<DateFormat> DATE_FORMAT_THREADLOCAL = new ThreadLocal<DateFormat>(){@Overrideprotected DateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");}};public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(5);for (int i = 0; i < 9; i++) {
// executorService.execute( () -> {
// try {
// System.out.println("parse: "+DATE_FORMAT_THREADLOCAL.get().parse("2023-04-24"));
// System.out.println("format: "+DATE_FORMAT_THREADLOCAL.get().format(new Date()));
// } catch (Exception e) {
// e.printStackTrace();
// }
// });// 会报错,因为SimpleDateFormat不是线程安全的executorService.execute(() -> {try {System.out.println(SIMPLE_DATE_FORMAT.parse("2023-04-24"));} catch (ParseException e) {throw new RuntimeException(e);}});}}
}
三、ThreadLocal副作用
为了使线程安全地共享某个变量, JDK 开出 了 ThreadLocal 这剂药方。但 “是药三分毒”, ThreadLocal 有一定的副作用 ,所以需要仔细阅读药方说明书,了解药性和注意事项。 ThreadLocal 的主要问题是会产生脏数据和内存泄漏。这两个问题通常是在线程池的线程中使用 ThreadLocal 引 发的,因为线程池有线程复用和内存常驻两个特点。
1. 脏数据
线程复用会产生脏数据。由于结程池会重用 Thread 对象 ,那么与 Thread 绑定的类的静态属性ThreadLocal 变量也会被重用。如果在实现的线程 run() 方法体中不显式地调用 remove() 清理与线程相关的 TbreadLocal 信息,那么倘若下一个结程不调用set() 设置初始值,就可能 get() 到重用的线程信息,包括 ThreadLocal 所关联的线程对象的 value 值。
脏数据问题在实际故障中十分常见。比如 , 用户 A 下单后没有看到订单记录,而用户 B 却看到了用户 A 的订单记录。通过排查发现是由于 session 优化引 发的。在原来的请求过程中,用户每次请求 Server , 都需要通过 sessionld 去缓存里查询用户的session 信息,这样做无疑增加了一次调用。因此,开发工程师决定采用某框架来缓存每个用户对应的SecurityContext , 它封装了 session 相关信息。优化后虽然会为每个用户新建一个 session 相关的上下文,但是由于Threadlocal 没有在线程处理结束时及时进行 remove() 清理操作 , 在高并发场景下,线程池中的线程可能会读取到上一个线程
缓存的用户信息。为了便于理解,用一段简要代码来模拟,如下所示:
package com.example.demo.test;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @Author: Ron* @Create: 2023-04-24 10:16*/
public class DirtyDataInThreadLocal {public static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {// 使用固定大小为1的线程池,说明上一个线程属性会被下一个线程属性复用ExecutorService pool = Executors.newFixedThreadPool(1);for (int i = 0; i < 2; i++) {Mythread mythread = new Mythread();pool.execute(mythread);}}private static class Mythread extends Thread {private static boolean flag = true;@Overridepublic void run() {if (flag) {// 第1个线程set后,并没有进行remove操作// 而第二个线程由于某种原因没有进行set操作,导致第二个线程get到了第一个线程的数据threadLocal.set(this.getName() + ", session info.");flag = false;}System.out.println(this.getName() + " 线程是 " + threadLocal.get());}}
}
执行结果如下:
2. 内存泄漏
在源码注释中提示使用 static 关键字来修饰 ThreadLocal。在此场景下 ,寄希望于ThreadLocal 对象失去引用后 , 触发弱引用机制来回收 Entry 的 Value 就不现实了。在上例中,如果不进行 remove() 操作 , 那么这个线程执行完成后,通过 ThreadLocal 对象持有的 String 对象是不会被释放的。
以上两个问题的解决办法很简单,就是在每次用完 ThreadLocal 时, 必须要及时调用 remove() 方法清理。
相关文章:
码出高效:Java开发手册笔记(java对象四种引用关系及ThreadLocal)
码出高效:Java开发手册笔记(java对象四种引用关系及ThreadLocal) 前言一、引用类型二、ThreadLocal价值三、ThreadLocal副作用 前言 “水能载舟,亦能覆舟。”用这句话来形容 ThreadLocal 最贴切不过。ThreadLocal 初衷是在线程并…...
为什么要进行数据决策?数据决策对企业而言有何重要意义?
“大数据”几乎已成为时下最时髦的词汇,不夸张地说,当今各行各业无不对大数据充满了向往,希望自己在新一轮的大数据营销中抢占先机。同时,从大数据中引申出的数据挖掘、数据分析、数据安全等数据运用技术也成为人们热捧的焦点。 …...
2. Java 异常体系
2.1 Throwable java.lang.Throwable 类是 Java 程序执行过程中发生的异常事件对应的类的根父类。 Throwable 中的常用方法: public void printStackTrace():打印异常的详细信息。 包含了异常的类型、异常的原因、异常出现的位置、在开发和调试阶段都得…...
如何学好STM32,需要哪些步骤?
学习STM32应用于项目开发需要以下步骤: 学习STM32的基本知识:包括STM32的架构、寄存器、外设等,理解STM32的工作原理和基本操作方法。 学习嵌入式系统和RTOS的基础知识:了解嵌入式系统的概念、RTOS的基本原理和使用方法ÿ…...
武忠祥老师每日一题||不定积分基础训练(四)
∫ d x 1 x 3 \int \frac{\rm dx}{1x^3} ∫1x3dx 解法一: 待定系数法: ∫ d x 1 x 3 \int \frac{dx}{1x^3} ∫1x3dx ∫ d x ( 1 x ) ( x 2 − x 1 ) \int \frac{dx}{(1x)(x^2-x1)} ∫(1x)(x2−x1)dx 1 3 ∫ ( 1 x 1 − x 2 x 2 − x …...
记一次产线打印json导致的redis连接超时
服务在中午十一点上线后,服务每分钟发出三到四次redis连接超时告警。错误信息为: Dial err:dial tcp: lookup xxxxx: i/o timeout 排查过程 先是检查redis机器的情况,redis写入并发数较大,缓存中保留了一小时大概400w条数据。red…...
FPGA入门系列12--RAM的使用
文章简介 本系列文章主要针对FPGA初学者编写,包括FPGA的模块书写、基础语法、状态机、RAM、UART、SPI、VGA、以及功能验证等。将每一个知识点作为一个章节进行讲解,旨在更快速的提升初学者在FPGA开发方面的能力,每一个章节中都有针对性的代码…...
【三十天精通Vue 3】第二十六天 Vue3 与 TypeScript 最佳实践
✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: 三十天精通 Vue 3 文章目录 引言一、为什么使用TypeScript?二、Vue 3和TypeScript…...
ffmpeg-mov-metadate不识别Bug修复
文章目录 BUG起因类似问题反馈问题解决具体步骤: 阅读过文章ffmpeg命令行解析调试流程记录movenc.c源码分析 BUG起因 在ffmpeg参数默认可识别的metadata参数如下: 具体可见libavformat/movenc.c->mov_write_udta_tag() mov_write_string_metadata(s,…...
(8)(8.6) 引导程序更新
文章目录 前言 1 我在哪里可以下载最新的引导程序? 2 使用任务规划器进行升级...
汽车电路图、原理框图、线束图、元器件布置图的识读技巧与要点
摘要: 想要读懂汽车电路图就必须把电的通路理清楚,即某条线是什么信号,该信号是输入信号、输出信号还是控制信号以及信号起什么作用,在什么条件下有信号,从哪里来,到哪里去。 一、汽车电路图的识读技巧 1.…...
( 数组和矩阵) 667. 优美的排列 II ——【Leetcode每日一题】
❓667. 优美的排列 II 难度:中等 给你两个整数 n 和 k ,请你构造一个答案列表 answer ,该列表应当包含从 1 到 n 的 n 个不同正整数,并同时满足下述条件: 假设该列表是 answer [a1, a2, a3, ... , an] ࿰…...
【python基础语法七】python内置函数和内置模块
内置全局函数 abs 绝对值函数 print(abs(-1)) # 1 print(abs(100)) # 100round 四舍五入 """奇进偶不进(n.5的情况特定发生)""" res round(3.87) # 4 res round(4.51) # 5 # res round(2.5) # 2 # res round(3.5) # 4 res round(6.5) # …...
81. read readline readlines 读取文件的三种方法
81. read readline readlines 读取文件的三种方法 文章目录 81. read readline readlines 读取文件的三种方法1. 读取文件的三种方法2. read方法3. readline方法4. readlines方法5. 代码总结5.1 read方法读取全部内容5.2 readline方法读取一行,返回字符串5.3 readli…...
【社区图书馆】【图书活动第四期】
目录 一、前言 二、作者简介 三、《PyTorch高级机器学习实战》内容简介 四、书目录 一、前言 今天,偶尔逛到csdn社区图书馆,看到有活动 “【图书活动第四期】来一起写书评领实体奖牌红包电子勋章吧!”(活动到今天结束…...
webpack学习指南(上)
构建流程 Webpack 的构建流程可以分为以下几个步骤: 解析配置文件:Webpack 会读取项目中的 webpack.config.js 文件,并解析其中的配置项。 解析入口文件:Webpack 通过配置文件中设置的 entry 入口,递归地解析出所有依…...
刷题记录˃ʍ˂
一、1033. 移动石子直到连续 思路 这道题是一道数学题,它一共分为三种可能 第一种可能为三个石子本来就是连续的时候 第二种可能为最少步数为1的时候,相邻石子不能大于一格 第三种可能为最少步数为2的时候,这时相邻石子大于一格 那么第二…...
Word2vec原理+实战学习笔记(二)
来源:投稿 作者:阿克西 编辑:学姐 前篇:Word2vec原理实战学习笔记(一) 视频链接:https://ai.deepshare.net/detail/p_5ee62f90022ee_zFpnlHXA/6 5 对比模型(论文Model Architectur…...
什么是Java的多线程?
Java的多线程是指在同一时间内,一个程序中同时运行多个线程。每个线程都是一个独立的执行路径,可以独立地执行代码。Java中的多线程机制使得程序可以更高效地利用计算机的多核处理器和CPU时间,从而提高程序的性能和响应能力。 创建和使用Jav…...
“use strict“是什么? 使用它有什么优缺点?
严格模式 - JavaScript | MDN Javascript 严格模式详解 - 阮一峰的网络日志 1、"use strict" 是什么? "use strict" :指定代码在严格条件下执行; 2、 使用 "use strict" 有什么优缺点? ① 严格模式通过抛出错…...
【C++】C++11常用特性总结
哥们哥们,把书读烂,困在爱里是笨蛋! 文章目录 一、统一的列表初始化1.统一的{}初始化2.std::initializer_list类型的初始化 二、简化声明的关键字1.decltype2.auto && nullptr 三、STL中的一些变化1.新增容器:array &…...
泛型——List 优于数组
数组与泛型有很大的不同: 1. 数组是协变的(covariant) 意思是:如果Sub是Super的子类型,则数组类型Sub[] 是数组类型Super[] 的子类型。 2. 泛型是不变的(invariant) 对于任何两种不同的类型Ty…...
JavaScript中对象的定义、引用和复制
JavaScript是一种广泛使用的脚本语言,其设计理念是面向对象的范式。在JavaScript中,对象就是一系列属性的集合,每个属性包含一个名称和一个值。属性的值可以是基本数据类型、对象类型或函数类型,这些类型的值相互之间有着不同的特…...
JavaScript通过函数异常处理来输入圆的半径,输出圆的面积的代码
以下为实现通过函数异常处理来输入圆的半径,输出圆的面积的代码和运行截图 目录 前言 一、通过函数异常处理来输入圆的半径,输出圆的面积 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择,您可以…...
Ubuntu 安装 Mysql
主要内容 本文主要是实现在虚拟机 Ubuntu 18.04 成功安装 MySQL 5.7,并实现远程访问功能,以 windows 下客户端访问虚拟机上的 mysql 数据库。 1. 切换至 root 用户 ,shell 终端指令均执行在 root 用户下 sudo su 2. 安装并设置 mysql 安…...
【五一创作】【Midjourney】Midjourney 连续性人物创作 ② ( 获取大图和 Seed 随机种子 | 通过 seed 随机种子生成类似图像 )
文章目录 一、获取大图和 Seed 随机种子二、通过 seed 种子生成类似图像 一、获取大图和 Seed 随机种子 注意 : 一定是使用 U 按钮 , 在生成的大图的基础上 , 添加 信封 表情 , 才能获取该大图的 Seed 种子编码 ; 在上一篇博客生成图像的基础上 , 点击 U3 获取第三张图的大图 ;…...
分布式事务 --- Seata事务模式、高可用
一、事务模式 1.1、XA模式 XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。…...
SQL(基础)
DDL: 数据定义语言 Definition,用来定义数据库对象(数据库、表、字段)CREATE、DROP、ALTER DML: 数据操作语言 Manipulation,用来对数据库表中的数据进行增删改 INSERT、UPDATE、DELETE 注意: DDL是改变表的结构 DML…...
「OceanBase 4.1 体验」OceanBase 4.1社区版的部署及使用体验
「OceanBase 4.1 体验」OceanBase 4.1社区版的部署及使用体验 一、前言1.1 本次实践介绍1.2 本次实践目的 二、准备环境资源2.1 部署前需准备工作2.2 本地环境规划 三、部署Docker环境3.1 安装Docker3.2 配置Docker镜像加速3.3 开启路由转发3.4 重启Docker服务 四、检查本地Doc…...
计算机操作系统实验:银行家算法模拟
目录 前言实验目的实验内容实验原理实验过程代码如下代码详解算法过程运行结果 总结 前言 本文是计算机操作系统实验的一部分,主要介绍了银行家算法的原理和实现。银行家算法是一种用于解决多个进程对多种资源的竞争和分配的算法,它可以避免死锁和资源浪…...
电脑做网站怎么解析域名/网页开发
我们可以使用UIWebView加载包含HTML标签以及样式但是后台返回HTML只是HTML的一部分包含HTML标签的文字内容加载到UILable 或者 UITextView中的text中今日,GMIC 2016正式在北京开幕,开幕仪式上,中关村发展集团总经理周云帆作了致辞演讲&#x…...
网站建设毕业设计模板/学技术的培训学校
mysql #1062 –Duplicate entry 1 for key PRIMARY更新时间:2012年07月24日 23:50:27 作者:Mysql进行数据备份,还原后进行回帖,出现以下错误代码,其实主要是导入数据重复的问题,将现在的数据表清空,重新导…...
什么是网站名称/谷歌paypal下载
开启三台虚拟机 实战:使用varnish加速多个不同域名站点的web服务器 varnish:192.168.80.100 //需要联网 web1:192.168.80.101——www.aa.com web2:192.168.80.102——www.bb.com 三台服务器全都要操作 systemctl stop f…...
福州专业网站建设网络公司/百度应用商店官网
在说LINQ之前必须先说说几个重要的C#语言特性 一:与LINQ有关的语言特性1.隐式类型(1)源起在隐式类型出现之前,我们在声明一个变量的时候,总是要为一个变量指定他的类型甚至在foreach一个集合的时候,也要为遍历的集合的元素&#x…...
网站群建设调研报告/seo优化工具大全
文件名称为 build.gradle 所在行内容为 #appVersionCode : 20220811, 取出shell的脚本为 APP_VERSION_CODE$(egrep "appVersionCode :(.*?)," build.gradle -o | sed s/appVersionCode : //g | sed s/\,//g | sed s/ //g)sed语法解释下 替换文本 sed s/…...
做汽车网站怎么挣钱吗/网络宣传方式有哪些
Windows Server2012R2已经是很老的版本了,估计还在用的人不多。今天恰好碰到一台内部用的服务器,出现了启动以后任务栏卡死的情况,经过几番努力,终于解决了问题,记录一下,估计其他版本的系统可能也能用得上…...