源码系列 之 ThreadLocal

news2024/11/24 7:51:01

简介

   ThreadLocal的作用是做数据隔离,存储的变量只属于当前线程,相当于当前线程的局部变量,多线程环境下,不会被别的线程访问与修改。常用于存储线程私有成员变量上下文,和用于同一线程,不同层级方法间传参等。JDK 1.8 中的 ThreadLocal 共741行代码,其中包含3个成员变量,13个成员方法和两个内部类。 我们先来看下核心原理,再来详细看下源码。

问题

我们可以带着问题去学习这部分内容,希望学习完后,能回答这些问题:

  1. ThreadLocal能不能代替Synchronized?和Synchronized的区别是什么?
  2. Thread、ThreadLocal、ThreadLocalMap的关系是怎么样的?
  3. 存储在jvm的堆还是栈中?
  4. ThreadLocal会导致内存泄漏吗,为什么?
  5. ThreadLocalMap为什么用Entry数组而不是Entry对象?
  6. ThreadLocal里的对象一定是线程安全的吗?
  7. ThreadLocalMap只用单纯的数组存值吗?如果出现哈希冲突怎么存值?

核心原理

   这个要从java.lang.Thread类说起,每个Thread对象中都拥有一个ThreadLocalMap(ThreadLocal的内部类)的成员变量。ThreadLocalMap内部又拥有一个Entry数组,每个Entry是一个键值对,key是ThreadLocal本身,value是ThreadLocal的泛型值。 (这里Thread类虽然有ThreadLocalMap成员变量,但没有get(),set(),remove()等增删改查的方法,其实就是通过ThreadLocal来操作的。)
   例如我们每一次请求,就是一个线程,然后一个线程里就有且只有一个ThreadLocalMap,然后我们的业务里可能new了好几个ThreadLocal对象,存了几个ThreadLocal的值,这些就存在Entry数组中,然后,我们根据当前线程当前ThreadLocal就能找到唯一的<T>value值。再简单点讲:每个线程里都有一个成员变量,本质是一个数组,里面存的就是你想线程私有化的几个对象
结构图如下:
在这里插入图片描述
核心源码如下:

// java.lang.Thread类里持有ThreadLocalMap的引用
public class Thread implements Runnable {
	... ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ... ...
}

// java.lang.ThreadLocal有内部静态类ThreadLocalMap,主要提供给Thread类使用
public class ThreadLocal<T> {
	
	... ...
	
    static class ThreadLocalMap {
    	// 存线程私有变量
        private Entry[] table;
        
        // ThreadLocalMap内部有Entry类,Entry的key是ThreadLocal本身,value是泛型值
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }

	... ...
}

ThreadLocal的成员变量

  这里介绍一下ThreadLocal的成员变量。

  • private final int threadLocalHashCode = nextHashCode()
    自定义的哈希值,主要是调用nextHashCode()方法获取。
  • private static AtomicInteger nextHashCode = new AtomicInteger()
    下一个要给出的哈希码。自动更新。从0开始。
  • private static final int HASH_INCREMENT = 0x61c88647;
    连续生成的哈希码之间的差异,此为一个魔数。

ThreadLocal的成员方法

  这里介绍一下ThreadLocal的成员方法。

  • public ThreadLocal()
    构造器。
	public ThreadLocal() {
    }
  • private static int nextHashCode()
    返回下一个哈希码。
	private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
  • protected T initialValue()
    用以初始化值,这个方法将在线程第一次使用get方法访问变量时被调用,如果调用get()前使用了set()设置值,则不会被调用,这里只是简单的初始化成了null,如果需要初始化成其它值,则必须将ThreadLocal子类化,并重写此方法。
// 由子类提供实现。
// protected
protected T initialValue() {
   return null;
}
  • public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
    可根据提供的函数,生成初始值。
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
  • public T get()
    返回该当前线程对应的线程局部变量值。
public T get() {
    // 获取当前线程,这里的currentThread()是个native方法
    Thread t = Thread.currentThread();
    // 获取当前线程对应的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    // 若获取到了。则获取此ThreadLocalMap下的entry对象,若entry也获取到了,那么直接获取entry对应的value返回即可
    if (map != null) {
        // 获取此ThreadLocalMap下的entry对象,把当前ThreadLocal当参数传进去
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 若entry也获取到了
        if (e != null) {
            @SuppressWarnings("unchecked")
            // 直接获取entry对应的value返回
            T result = (T)e.value;
            return result;
        }
    }
    // 若没获取到ThreadLocalMap或没获取到Entry,则设置初始值
    //  初始值方法是延迟加载
    return setInitialValue();
}
  • boolean isPresent()
    如果当前线程中ThreadLocalMap不为空,且该局部变量也不为空,则返回true,否则返回false。
	boolean isPresent() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        return map != null && map.getEntry(this) != null;
    }
  • private T setInitialValue()
    设置初始值,调用initialValue。
// 设置初始值
private T setInitialValue() {
    // 调用初始值方法,由子类提供。
    T value = initialValue();
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取map
    ThreadLocalMap map = getMap(t);
    // 获取到了
    if (map != null)
        // set
        map.set(this, value);
    else
        // 没获取到。创建map并赋值
        createMap(t, value);
    // 返回初始值。
    return value;
}
  • public void set(T value)
    设置当前线程的线程局部变量的值。
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程对应的ThreadLocalMap实例
    ThreadLocalMap map = getMap(t);
    // 若当前线程有对应的ThreadLocalMap实例,则将当前ThreadLocal对象作为key,value做为值存到ThreadLocalMap的entry里。
    if (map != null)
        map.set(this, value);
    else
        // 若当前线程没有对应的ThreadLocalMap实例,则创建ThreadLocalMap,并将此线程与之绑定
        createMap(t, value);
}
  • public void remove()
    删除当前线程局部变量的值,目的是为了减少内存占用,和防止内存泄漏。
public void remove() {
    // 获取当前线程的ThreadLocalMap对象,并将其移除。
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
  • ThreadLocalMap getMap(Thread t)
    获取ThreadLocalMap,在你调用ThreadLocal.get()方法的时候就会调用这个方法,它的返回是当前线程里的threadLocals的引用。
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
  • void createMap(Thread t, T firstValue)
    创建ThreadLocalMap,ThreadLocal底层其实就是一个map来维护的。
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// ThreadLocalMap构造器。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // new了一个ThreadLocalMap的内部类Entry,且将key和value传入。
    // key是ThreadLocal对象。
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
  • static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap)
    用工厂方法创建继承的线程局部变量的映射。
	static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
  • T childValue(T parentValue)
    childValue()是用来在ThreadLocal子类中定义实现的,在这里定义,主要是提供给createInheritedMap工厂方法调用。
T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

ThreadLocal的内部类

  这里介绍一下ThreadLocal的内部类。

  • static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {}
    ThreadLocal的扩展,从指定的提供者获取初始值。
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();
        }
    }
  • static class ThreadLocalMap {}
    ThreadLocalMap是一个定制的散列映射,只适合维护线程本地值。ThreadLocalMap用类似HashMap的方式,存储ThreadLocal和他对应泛型的值,只不过这里只单纯的用了数组没有用到链表。没有用链表,怎么解决哈希冲突问题呢?其实很简单,依次向下一个索引查找,把值存在下一个为null的位置。
	static class ThreadLocalMap {

        /**
         * ThreadLocalMap 里数组里具体存的值
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /**
            * 与当前ThreadLocal 对应的值
            */
            Object value;

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

        /**
         * 初始容量必须是2的幂次数,当前默认为16
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * ThreadLocalMap 里的Entry[] 数组,长度必须为2的幂次数
         */
        private Entry[] table;

        /**
         * 当前 Entry[] table 的长度
         */
        private int size = 0;

        /**
         * 阈值,超过后需扩容
         */
        private int threshold; // Default to 0

        /**
         * 设置阈值为长度的 三分之二
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * I对len取模
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * 获取前一个索引值
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * 构造方法,懒加载的
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        	// 根据初始容量,初始化表
            table = new Entry[INITIAL_CAPACITY];
            // 获取当前哈希值后,对len进行取模,确定索引位置
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            // 初始化长度,和阈值
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        /**
         * 从给定父映射创建新映射         
         * */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
        	// 初始化参数
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (Entry e : parentTable) {
                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++;
                    }
                }
            }
        }

        /**
         * 查找与key相关联的条目
         */
        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
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * 根据key值找不到Entry时,用以下方法找,当前i值找不到,就到i+1处找
         */
        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)
                    expungeStaleEntry(i);
                else
                	// 当前索引 i 处找不到,就到索引 i + 1 处查找,这也是ThreadLocalMap解决哈希冲突的方法,即,当前有值,则顺位往下一个索引存
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * 根据ThreadLocal,存对应value值
         */
        private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            // 用key的哈希值,对len取模,以计算存储的索引位置
            int i = key.threadLocalHashCode & (len-1);

			// 这几行代码就很有意思了,前面说ThreadLocalMap是没有链表的,那么怎么解决哈希冲突问题呢
			// 就是,依次往下一位索引存,直到有空位为止
            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

				// 如果key相等,则更新值
                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        /**
         * 删除指定key的节点
         */
        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;
                }
            }
        }

        /**
         * 替换已经不再被使用的旧值,(key为null的值       
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();


                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

        /**
         * staleSlot和下个空槽之间的所有空槽都将被检查和清除
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

			// 清楚当前槽
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                // 如果发现key为空,则清除value值
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

        /**
         * 启发式地扫描一些单位,寻找陈旧的条目
         */
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

        /**
         * 清除旧条目后,长度依然3/4threshold,则扩容
         */
        private void rehash() {
            expungeStaleEntries();

            if (size >= threshold - threshold / 4)
                resize();
        }

        /**
         * 将原来的容量,扩大为两倍
         */
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (Entry e : oldTab) {
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    // 清除旧表无用值
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                    	// 找到合适位置并存储
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }
			
			// 更新成员变量
            setThreshold(newLen);
            size = count;
            table = newTab;
        }

        /**
         * 删除表中所有陈旧的条目
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }

问题解答

  相信看了上面的源码,文章开头的部分问题,你已经有了自己的答案,接下来我们再对一下答案。
1. ThreadLocal能不能代替Synchronized?和Synchronized的区别是什么?
  答:ThreadLocal肯定不能代替Synchronized,ThreadLocal只是让变量变成了完全私有化,别的线程是无法访问的。而Synchronized除了解决线程冲突外,更重要的是,可以使变量被所有线程访问和修改。
2.Thread、ThreadLocal、ThreadLocalMap的关系是怎么样的?
  答:关系有点绕,Thread类中包括ThreadLocalMap成员变量,ThreadLocalMap是ThreadLocal的内部类,ThreadLocalMap有Entry数组,Entry实体是键值对,其中key即是ThreadLocal类型。
3 存储在jvm的堆还是栈中?
  答:很会人会觉得变量变成线程私有了,就一定是存在虚拟机栈中的,其实不是,我们私有变量是存在Thread对象里的,而对象都是存在堆里的,所以ThreadLocal的实例和他的值都是存在堆上的。

4. ThreadLocal会导致内存泄漏吗,为什么?
  答:这个要从两方面分析,即ThreadLocalMap.Entry的key和value值分别讲:key直接是交给了父类处理super(key),父类是个弱引用,所以key完全不存在内存泄漏问题,value是个强引用,如果线程终止了,也会被GC干掉,但有时线程是不会被终止的,比如线程池里的核心线程,此时引用链就变成了:Thread->ThreadLocalMap->Entry(key为null)->value,由于value和Thread还存在链路关系,还是可达的,所以不会被回收,这样越来越多的垃圾对象产生却无法回收,最终可能导致OOM,当然解决办法也简单,用完私有变量后使用remove()方法即可,它会删除所有value值。

5. 为什么用Entry数组而不是Entry对象?
  答:在同一个线程里,我们可能需要多个线程私有变量,所以需要数组。

6. ThreadLocal里的对象一定是线程安全的吗?
  答:不一定,因为ThreadLocal.set()进去的对象,可能本身可能就是可供多个线程访问的,比如static对象,这样是没办法保存线程安全的。

7. ThreadLocalMap只用单纯的数组存值吗?如果出现哈希冲突怎么存值?
  答:ThreadLocalMap是只用数组存Entry值,如果 i 位置出现哈希冲突,则存在 i + 1处,如果 i + 1 也不为空,则依次往下顺延,直到找到空位为止。

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

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

相关文章

Jenkins 笔记

Jenkins brew install jenkins-lts brew services restart jenkins-lts brew services stop jenkins-lts b999ff5683464346b6d083f894968121 l 软件构建自动化 &#xff1a;配置完成后&#xff0c;CI系统会依照预先制定的时间表&#xff0c;或者针对某一特定事件&#xff0c;…

进阶C语言第三章-------《字符函数和内存函数》 完整思维导图+基本练习题+深入细节+通俗易懂+知识点+建议收藏

绪论 书接上回&#xff0c;通过进阶指针你已经了解到了更多种指针类型&#xff0c;他们的用法及使用之处相当的关阔需要不断的积累经验来使用&#xff0c;这里我毛遂自荐一下我的指针练习希望对你有帮助&#xff0c;本章是一些关于字符串的函数介绍和自己实现&#xff0c;总体来…

数据结构与算法基础-学习-11-线性表之链栈的初始化、判断非空、压栈、获取栈长度、弹栈、获取栈顶元素

一、个人理解链栈相较于顺序栈不存在上溢&#xff08;数据满&#xff09;的情况&#xff0c;除非内存不足&#xff0c;但存储密度会低于顺序栈&#xff0c;因为会多存一个指针域&#xff0c;其他逻辑和顺序表一致。总结如下&#xff1a;头指针指向栈顶。链栈没有头节点直接就是…

智慧校园应用系统建设方案

系统简介 建设“智慧校园”的核心是创新新形势下高校发展理念、推进高校建设转型、变革高校管理行为方式&#xff1b;建设“智慧校园”的目的是推进信息化进入高校、服务管理、惠及师生&#xff1b;建设“智慧”的目标是实现 “信息采集、数据整合、信息促管、信息育人、信息联…

linux之echo使用技巧

参考文章&#xff1a;linux基本功系列-echo命令实战一、echo 命令是什么&#xff1f;作用&#xff1a; echo命令能将指定文本显示在Linux命令行上&#xff0c;或者通过重定向符写入到指定的文件中。语 法&#xff1a;echo [-ne][字符串] / echo [–help][–version]补充说明&am…

VMware安装CentOS7

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…

Java+Python+HanLP自然语言处理包实现解析文本分词

目录 需要安装 Python部分 Java部分 需要安装 HanLP官网Api教程 https://bbs.hanlp.com/t/hanlp2-1-restful-api/53 首先需要电脑安装Python环境并配置环境变量&#xff08;网上搜就可以一大堆教程&#xff09;&#xff0c;建议Python版本3 然后根据HanLP官网教程安装hanl…

「JVM 原理使用」 实际开发中的应用

Class 文件格式、执行引擎主要以 Class 文件描述了存储格式、类何时加载、如何连接、VM 如何执行字节码指令&#xff0c;这些动作基本都是 JVM 直接控制&#xff0c;用户代码无法干预和改变&#xff1b; 用户可以干预的只有字节码生成、类加载器两部分&#xff0c;而这两部分的…

Android 面试 基础知识汇总

Android常用的设计模式HTTP和HTTPS的区别TCP/UDP的区别

PendingIntent兼容安卓12 的一个问题

这是我第一次遇到这个问题&#xff0c;反正我也是第一次做完整的安卓app&#xff0c;很多个第一次。我之前做的那个老旧手机做桌面摆件的app&#xff0c;因为原来的“无干预”版本无法使用&#xff0c;所以又用回了“和风版”&#xff0c;但这个版本很久没用了&#xff0c;有些…

实用机器学习-学习笔记

文章目录9.1模型调参9.1.1思考与总结9.1.2 基线baseline9.1.3SGD ADAM9.1.4 训练代价9.1.5 AUTOML9.1.6 要多次调参管理9.1.7复现实验的困难9.1模型调参 9.1.1思考与总结 1了解了baseline和调参基本原则 2了解了adams和sgd的优劣 3了解了训练树和神经网络的基本代价 4了解了a…

【蓝桥杯集训2】差分专题(3 / 4)

目录 差分模板 1、一维差分 2、二维差分 3729. 改变数组元素 - 差分 区间修改 100. 增减序列 - 差分模板 1、一维差分 活动 - AcWing 给a数组 [l,r] 区间的每个数c&#xff0c;只需要给其差分数组b做如下操作即可 b[l]c; b[r1]-c; 差分数组 a[]是b[]的前缀和数组 如果…

倒计时100天 | DBF深圳国际户外运动博览会5月一起狂飙初夏

狂飙初夏的大型户外运动嘉年华即将登场&#xff01;2023年5月26日至28日&#xff0c;第四届DBF深圳国际户外运动博览会&#xff08;简称DBF深圳户外展&#xff09;将于深圳国际会展中心&#xff08;宝安&#xff09;举办。本届展会展览面积超过7万平方米&#xff0c;预计将吸引…

保姆级Vue3+Vite项目实战黑白模式切换

写在前面注&#xff1a;本文首发掘金签约专栏&#xff0c;此为文章同步&#xff01;本文为 Vue3Vite 项目实战系列教程文章第四篇&#xff0c;系列文章建议从头观看效果更佳&#xff0c;大家可关注专栏防走失&#xff01;点个赞再看有助于全文完整阅读&#xff01;此系列文章主…

C/C++【内存管理】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f38a;每篇一句&#xff1a; 图片来源 Love is a choice. It is a conscious commitment. It is something you choose to make work every day with a person who has chosen the same thi…

商城系统必备营销工具(五)——积分商城

做商城&#xff0c;流量必不可少&#xff0c;日活跃度也很重要。现在各大APP、网站、小程序和微商城&#xff0c;基本都在为了巩固流量做积分商城&#xff0c;虽然已经随处可见&#xff0c;但很多企业商家却并没有将积分商城运作起来&#xff0c;积分商城也没有人浏览兑换商品。…

跟ChatGPT,聊聊ChatGPT

不仅“上知天文、下知地理”&#xff0c;似乎还能对答如流、出口成诗&#xff0c;甚至还能写剧本、编音乐、写代码——最近&#xff0c;一款名叫ChatGPT的人工智能聊天机器人火爆全球。由此&#xff0c;一系列关于新一代技术变革、人工智能替代人力、巨头企业扎堆入局AI的讨论在…

Multi Paxos

basic paxos 是用于确定且只能确定一个值&#xff0c;“只确定一个值有什么用&#xff1f;这可解决不了我面临的问题,例如每个用户都要多次保存数据.” 你心中可能有这样的疑问。 原simple paxos论文里有提到一连串个instance of paxos [4] 但没有提出 multi paxos的概念&…

ChatGPT国内镜像站试用,聊天、Python代码生成。

ChatGPT国内镜像站试用&#xff0c;聊天、Python代码生成。 (本文获得CSDN质量评分【91】)【学习的细节是欢悦的历程】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单…… …

前端开发:关于diff算法详解

前言 前端开发中&#xff0c;关于JS原生的内容和前端算法相关的内容一直都是前端工作中的核心&#xff0c;不管是在实际的前端业务开发还是前端求职面试&#xff0c;都是非常重要且必备的内容。那么本篇博文来分享一个关于前端开发中必备内容&#xff1a;diff算法&#xff0c;d…