Java——聊聊JUC中的ThreadLocal

news2024/12/25 12:20:00

文章目录:

1.什么是ThreadLocal?

1.1 api介绍

1.2 最简单的案例认识ThreadLocal

1.3 线程池结合ThreadLocal案例

2.Thread &ThreadLocal & ThreadLocalMap

3.ThreadLocal内存泄漏问题

3.1 四大引用之强引用

3.2 四大引用之软引用

3.3 四大引用之弱引用

3.4 四大引用之虚引用

3.5 ThreadLocal为什么是弱引用?

4.小总结


1.什么是ThreadLocal?

ThreadLocal提供线程局部变量,这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。 ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。

实现每一个线程都有自己专属的本地变量副本(自己用自己的变量不麻烦别人,不和其他人共享,人人有份,人各一份),主要解决了让每个线程绑定自己的值,通过使用get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全问题。

1.1 api介绍

ThreadLocal是位于 java.lang 包下的,从jdk官方文档中可以看到它所有的api。而它的构造方法其实就是 withInitial(Supplier)。

1.2 最简单的案例认识ThreadLocal

希望各自分灶吃饭,各凭销售本事提成,按照出单数各自统计。比如某房产中介销售都有自己的销售额指标,自己专属于自己的,不和别人掺和。

  • 使用ThreadLocal

  • 先初始化,给个0值

  • 利用set get方法

  • ------注意,也要调用remove() 接口,不然容易导致内存泄漏。(在阿里巴巴Java开发手册中也有说明)

package com.juc.threadlocal;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author: SongZiHao
 * @date: 2023/2/13
 */
class House {
    public int saleCount = 0;

    public synchronized void saleHouse() {
        ++saleCount;
    }

//    ThreadLocal<Integer> saleVolume = new ThreadLocal<Integer>() {
//        @Override
//        protected Integer initialValue() {
//            return 0;
//        }
//    };

    ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);

    public void saleVolumeByThreadLocal() {
        saleVolume.set(saleVolume.get() + 1);
    }
}

public class ThreadLocalDemo1 {
    public static void main(String[] args) {
        House house = new House();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                int size = new Random().nextInt(5) + 1;
                try {
                    for (int j = 0; j < size; j++) {
                        house.saleHouse();
                        house.saleVolumeByThreadLocal();
                    }
                    System.out.println(Thread.currentThread().getName() + " 号销售卖出多少套:" + house.saleVolume.get());
                } finally {
                    house.saleVolume.remove();
                }
            }, String.valueOf(i)).start();
        }
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 共计卖出多少套:" + house.saleCount);
    }
}

1.3 线程池结合ThreadLocal案例

根据阿里规范,需要对自定义的ThreadLocal进行回收,否则容易造成内存泄漏和业务逻辑问题(因为线程池中的线程会复用)。 

package com.juc.threadlocal;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author: SongZiHao
 * @date: 2023/2/13
 */
class MyData {
    ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

    public void add() {
        threadLocal.set(threadLocal.get() + 1);
    }
}

public class ThreadLocalDemo2 {
    public static void main(String[] args) {
        MyData data = new MyData();
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.submit(() -> {
                    try {
                        Integer beforeValue = data.threadLocal.get();
                        data.add();
                        Integer afterValue = data.threadLocal.get();
                        System.out.println(Thread.currentThread().getName() + " before: " + beforeValue + ", after: " + afterValue);
                    } finally {
                        data.threadLocal.remove();
                    }
                });
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

如果上面的代码没有 finally + remove 了话,那么每个线程的value值就会累积,变成2、3、4等等,这是因为这些线程会被复用。 


2.Thread &ThreadLocal & ThreadLocalMap

我们如果随便翻翻源码了话,可能会经常看到三个类:Thread,ThreadLocal,ThreadLocalMap。

  • 根据官方API,Thread是程序中执行的线程;ThreadLocal类提供线程局部变量

  • 先打开Thread.java类,发现每个Thread类里面有一个ThreadLocal类,而ThreadLocalMapThreadLocal的一个静态内部类。

下面是ThreadLocalMap这个静态内部类,熟悉HashMap的同学看到这里肯定不陌生,还是熟悉的配方、还是熟悉的味道,设置k-v键值对,初始化容量16。 

threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象
当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放。

JVM内部维护了一个线程版的Map<Thread, T>(通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中,每个线程要用到这个T的时候,用当前的线程去Map里面获取,通过这样让每个线程都拥有了自己独立的变量,人手一份,竞争条件被彻底消除,在并发模式下是绝对安全的变量。

每个Thread对象维护着一个ThreadLocalMap的引用。ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储。

  • 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值Value是传递进来的对象。
  • 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象。
  • ThreadLocal本身并不存储值,它只是自己作为一个key来让线程从ThreadLocalMap获取value,正因为这个原理,所以ThreadLocal能够实现“数据隔离”,获取当前线程的局部变量值,不受其他线程影响。

Thread就是一个大活人,ThreadLocal就是身份证原件,ThreadLocalMap就是身份证上的信息(姓名:张三;出生年月:xxxx年y月z日)这不就是个k-v的Map吗?

那么Thread,ThreadLocal,ThreadLocalMap三者可以用下面这张图概括。

在这里插入图片描述


3.ThreadLocal内存泄漏问题

3.1 四大引用之强引用

Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

官方API: finalize()的通常目的是在对象被不可撤销地丢弃之前执行清理操作。(我自己理解,某个对象被干掉前还会调用finalize()清理一下)

  • Reference是强引用
  • SoftReference是软引用
  • WeakReference是弱引用
  • PhantomReference是虚引用

在这里插入图片描述

强引用:

  1. 当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
  2. 强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。
  3. 对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,一般认为就是可以被垃圾收集的了(当然具体回收时机还是要看垃圾收集策略)。
package com.juc.threadlocal;

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: SongZiHao
 * @date: 2023/2/13
 */
class MyObject {
    //finalize方法一般不会重写,也不需要我们手动调用,这里为了演示案例才这样写
    @Override
    protected void finalize() throws Throwable {
        //在对象被不可撤销的丢弃之前执行清理操作
        System.out.println("---- invoke finalize method ----");
    }
}

public class ReferenceDemo {
    public static void main(String[] args) {
        strongReference();
    }

    /**
     * 默认模式:强引用
     */
    private static void strongReference() {
        MyObject obj = new MyObject();
        System.out.println("gc before: " + obj);
        obj = null;
        System.gc(); //演示案例这里手动gc,一般不会这样做
        System.out.println("gc after: " + obj);
    }
}

3.2 四大引用之软引用

软引用是一种相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。

对于只有软引用的对象来说,当系统内存充足时它 不会 被回收,当系统内存不足时它 会 被回收。

软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收!

演示案例先调整一下JVM的参数如下:👇👇👇

package com.juc.threadlocal;

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: SongZiHao
 * @date: 2023/2/13
 */
class MyObject {
    //finalize方法一般不会重写,也不需要我们手动调用,这里为了演示案例才这样写
    @Override
    protected void finalize() throws Throwable {
        //在对象被不可撤销的丢弃之前执行清理操作
        System.out.println("---- invoke finalize method ----");
    }
}

public class ReferenceDemo {
    public static void main(String[] args) {
        softReference();
    }

    /**
     * 软引用
     */
    private static void softReference() {
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());
        System.out.println("gc before 内存够用: " + softReference.get());
        System.gc(); //演示案例这里手动gc,一般不会这样做
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("gc after 内存仍够用: " + softReference.get()); //软引用,gc之后内存够用就不会回收对象
        try {
            byte[] bytes = new byte[20 * 1024 * 1024]; //20MB对象,内存肯定不够用了
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            System.out.println("gc after 内存不够用: " + softReference.get()); //gc之后内存不够用就会回收对象
        }
    }
}

3.3 四大引用之弱引用

弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短,

对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

package com.juc.threadlocal;

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: SongZiHao
 * @date: 2023/2/13
 */
class MyObject {
    //finalize方法一般不会重写,也不需要我们手动调用,这里为了演示案例才这样写
    @Override
    protected void finalize() throws Throwable {
        //在对象被不可撤销的丢弃之前执行清理操作
        System.out.println("---- invoke finalize method ----");
    }
}

public class ReferenceDemo {
    public static void main(String[] args) {
        weakReference();
    }

    /**
     * 弱引用
     */
    private static void weakReference() {
        WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
        System.out.println("gc before 内存够用: " + weakReference.get());
        System.gc();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("gc after 内存够用: " + weakReference.get());
    }
}

3.4 四大引用之虚引用

  1. 虚引用必须和引用队列 (ReferenceQueue)联合使用。虚引用需要java.lang.ret.PhantomReterence类来实现,顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
  2. PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被 finalize以后,做某些事情的通知机制。
  3. 处理监控通知使用,换句话说,设置虚引用关联对象的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理,用来实现比finalize机制更灵活的回收操作。

案例如下,记得先给JVM参数设置成-Xms10m -Xmx10m 。

package com.juc.threadlocal;

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: SongZiHao
 * @date: 2023/2/13
 */
class MyObject {
    //finalize方法一般不会重写,也不需要我们手动调用,这里为了演示案例才这样写
    @Override
    protected void finalize() throws Throwable {
        //在对象被不可撤销的丢弃之前执行清理操作
        System.out.println("---- invoke finalize method ----");
    }
}

public class ReferenceDemo {
    public static void main(String[] args) {
        phantomReference();
    }

    /**
     * 虚引用
     */
    private static void phantomReference() {
        MyObject obj = new MyObject();
        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
        PhantomReference<MyObject> phantomReference = new PhantomReference<>(obj, referenceQueue);
        System.out.println(phantomReference.get()); //虚引用get方法总是返回null
        List<byte[]> list = new ArrayList<>();
        new Thread(() -> {
            while (true) {
                //一共10M内存,死循环每次向list中存1MB数据
                list.add(new byte[1 * 1024 * 1024]);
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(phantomReference.get() + " list add ok....");
            }
        }, "t1").start();
        new Thread(() -> {
            while (true) {
                Reference<? extends MyObject> reference = referenceQueue.poll();
                if (reference != null) {
                    System.out.println("有虚对象被回收加入了队列....");
                    break;
                }
            }
        }, "t2").start();
    }
}

由于这里的代码执行结果不一定会成功(打印有虚对象被回收加入了队列这句话),所以我直接把周阳老师的图抓了过来。 

在这里插入图片描述

3.5 ThreadLocal为什么是弱引用?

在这里插入图片描述

当function01方法执行完毕后,栈帧销毁强引用 tl 也就没有了。但此时线程的ThreadLocalMap里某个entry的key引用还指向这个对象。
若这个key引用是强引用,就会导致key指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏;
若这个key引用是弱引用,就大概率会减少内存泄漏的问题。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收且Entry的key引用指向为null。


ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如正好用在线程池),这些key为null的Entry的value就会一直存在一条强引用链。

虽然弱引用,保证了key指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现key为null时才会去回收整个entry、value,因此弱引用不能100%保证内存不泄露。我们要在不使用某个ThreadLocal对象后,手动调用​方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。

  • 结论:在不使用某个ThreadLocal对象后,手动调用remove方法来删除它(尤其是防止线程池下的复用)
  • 从前面的set,get,remove方法的源码中可以看出,在threadLocal的生命周期里,针对threadLocal存在的内存泄漏的问题,都会通过expungeStaleEntry、cleanSomeSlots、replaceStaleEntry 这三个方法清理掉key为null的脏entry。

4.小总结

  1. 使用ThreadLocal一定要进行初始化,避免空指针问题。  ThreadLocal.withInitial(() -> 0)
  2. 建议把ThreadLocal修饰为static。

  3. 用完记得手动remove。

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

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

相关文章

cs285学习笔记

文章目录lec1ML和RL之间的区别几种RL分类current challengeslec4markov chainmarkov decision processpartially observed markov decision processRLs goalQ & Alec1 ML和RL之间的区别 mlrliid data数据不iid&#xff0c;前面的数据会影响future input训练时有确定的gro…

搜索二叉树

文章目录二叉搜索树模拟实现InsertInsertR()EraseEraseR搜索树的价值实现代码二叉搜索树 在二叉树的基础之上, 左子树的值都比根节点小&#xff0c;右子树都更大。那么他的左右子树也分别叫做二叉搜索树。 查找一个节点,最多查找高度次(建立在这个树是比较均衡的).10亿里面找…

Shennina:一款带有人工智能的自动化主机渗透工具

关于Shennina Shennina是一款功能强大的自动化主机渗透/漏洞利用框架&#xff0c;该项目的主要目的是使用人工智能技术来实现安全扫描、漏洞扫描/分析和漏洞利用开发的完全自动化。Shennina整合了Metasploit和Nmap这两款强大的网络安全工具实现其部分功能&#xff0c;并执行渗…

微搭使用笔记(二)微搭低代码平台介绍及基础使用

概述 官网地址&#xff1a; 官网 官方文档&#xff1a; 官方文档 FAQ: FAQ 腾讯云微搭低代码是一个高性能的低代码开发平台&#xff0c;用户可通过拖拽式开发&#xff0c;可视化配置构建 PC Web、H5 和小程序应用。支持打通企业内部数据&#xff0c;轻松实现企业微信管理、工…

别具一格,原创唯美浪漫情人节表白专辑,(复制就可用)(html5,css3,svg)表白爱心代码(4)

别具一格,独此一家&#xff0c;原创唯美浪漫情人节表白专辑 不一样的惊喜哦~&#xff01;&#xff08;html5,css3,svg)表白爱心代码&#xff08;复制就可用&#xff09;&#xff08;4&#xff09; 目录 款式四&#xff1a;时光的记忆款 1、拷贝完整源代码 2、更新时光盒所…

Springboot使用MDC进行日志追踪

Springboot使用MDC进行日志追踪前言一、为什么要跟踪日志二、MDC存储日志原理三、开始代码1、封装MDC工具类2、注册日志追踪拦截器四、配置logBack五、查看追踪效果六、要解决traceId传递问题1、在不同线程之间的传递2、远程调用时候的传递总结前言 MDC&#xff08;Mapped Diag…

基于ChatGPT +Node.js的基本使用

一、简介 最近,围绕ChatGPT和OpenAI的话题是层出不穷,国内外的技术工作者都掀起了一股学习OpenAI的技术浪潮,甚至有很多的媒体预测OpenAI将会带来行业的革命,而国外一些大的企业也将OpenAI视为重要的竞争对手,比如Google和微软。 事实上,OpenAI 可以应用于任何涉及理解…

SpringBoot(3)之包结构

根据spring可知道&#xff0c;注解之所以可以使用&#xff0c;是因为通过包扫描器&#xff0c;扫描包&#xff0c;然后才能通过注解开发。 那么springboot需要扫描哪里呢&#xff1f; springboot的默认包扫描器&#xff0c;扫描的是自己所在的包和子包&#xff0c;例子如下 我…

生成式AI对业务流程有哪些影响?企业如何应用生成式AI?一文看懂

集成与融合类ChatGPT工具与技术&#xff0c;以生成式AI变革业务流程ChatGPT背后的生成式AI&#xff0c;聊聊生成式AI如何改变业务流程ChatGPT月活用户过亿&#xff0c;生成式AI对组织的业务流程有哪些影响?生成式AI对业务流程有哪些影响?企业如何应用生成式AI?一文看懂业务流…

Transformer

Transformer由4部分组成&#xff0c;分别是&#xff1a;输入模块、编码模块、解码模块、输出模块整体架构图&#xff1a;一、输入模块结构 &#xff08;1&#xff09;源文本嵌入层及其位置编码器&#xff08;2&#xff09;目标文本嵌入层及其位置编码器二、编码器模块结构由N个…

【计算器】四则运算的算法实现

先实现整数部分 加减乘比较简单, 可以参考: 高精度算法全套&#xff08;加&#xff0c;减&#xff0c;乘&#xff0c;除&#xff0c;全网最详细&#xff09;. 除法我参考的是: 大数加减乘除运算总结 四则运算相关的OJ题目 四则远算 关于除法 基本上参考的是: 大数加减乘除…

数据结构:队列

队列 队列(Queue)是一个数据集合&#xff0c;仅允许在列表的一端进行插入&#xff0c;另一端进行删除。 进行插入的一端称为队尾(rear)&#xff0c;插入动作称为进队或入队 进行删除的一端称为队头(front)&#xff0c;删除动作称为出队 队列的性质:先进先出(First-in, First-o…

C++【二叉树进阶(二叉搜索树)】

文章目录前言1、二叉搜索树1-1、 二叉搜索树概念2、二叉搜索树操作2-1、树和节点的基本框架2-2、二叉搜索树的查找2-3、中序遍历2-4、二叉搜索树的插入2-5、二叉搜索树的删除3、二叉搜索树的模拟实现3-1、循环版本3-2、递归版本4、二叉搜索树的应用4-1、K模型4-2、KV模型4-3、K…

Linux 中断实验

目录 一、Linux 中断简介 上半部与下半部 二、添加设备树 三、编写驱动 1、定义宏 2、编写一个key结构体 3、imx6uirq设备添加成员 ​编辑4、按键中断处理函数 5、按键初始化 6、在驱动入口添加初始化 7、 驱动出口函数 代码如下 四、利用定时器进行消抖处理 1、添…

Spring Security in Action 第十四章 实现资源服务器端

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;以实战为线索&#xff0c;逐步深入SpringSecurity相关知识相关知识&#xff0c;打造完整的SpringSecurity学习步骤&#xff0c;提升工程化编码能力和思维能力&#xff0c;写出高质量代码。希望大家都能够从中有所收获&#…

ArcGIS中的附件功能

从ArcGIS10起,空间数据库增加了"附件"的功能,可灵活管理与要素相关的附加信息,可以是图像、PDF、文本文档或任意其他文件类型。例如,如果用某个要素表示建筑物,则可以使用附件来添加多张从不同角度拍摄的建筑物照片。 启动附件功能 要想使用附件功能,要素类必…

Docker 中遇到的问题

1&#xff1a;docker-tomcat 篇 第一天启动主机和虚拟机都可以正常访问&#xff0c;晚上睡觉的时候就挂起关机睡觉了&#xff0c;但到了第二天主机访问不了了&#xff0c;ping 也能ping 通&#xff0c;后来停掉容器&#xff0c;重启了虚拟机就好了&#xff0c;就很离谱。 这是成…

Web3CN|Damus刷频背后,大众在期待什么样的去中心化社交?

刚过去的一周&#xff0c;许多人的朋友圈包括Twitter、Faceboo在内都在被一串公钥字母刷屏&#xff0c;其重要起因就是 Twitter 前首席执行官 Jack Dorsey 发推称&#xff0c;&#xff08;2月1日&#xff09;基于去中心化社交协议 Nostr 的社交产品 Damus 和 Amethyst 已分别在…

互联网舆情监测系统的设计研究,TOOM舆情监测系统研究框架?

舆情监测研究分析是指通过对社会公众对某个事件、话题、品牌、政策等的态度和情绪进行收集、处理、分析和评估&#xff0c;帮助政府、企业、媒体等利益相关者及时掌握公众的反应&#xff0c;做好应对危机和制定舆情管理策略的工作&#xff0c;互联网舆情监测系统的设计研究&…

全志V853芯片 如何在Tina V85x平台切换sensor?

目的 V85x某方案目前默认Sensor是GC2053。实际使用时若需要用到GC4663&#xff08;比如wdr功能&#xff09;和SC530AI&#xff08;支持500W&#xff09;&#xff0c;可按如下步骤完成切换。 步骤 下面以GC4663为例&#xff0c;SC530AI按相应方式适配。 Step1 检查Sensor驱动…