Java并发编程(四)

ThreadLocal

1.ThreadLocal是什么

ThreadLocal类让每一个线程都拥有了自己的本地变量,这意味着每个线程都可以独立地、安全地操作这些变量,而不会影响其他线程。

ThreadLocal的常用API

  • get():获取当前线程中与ThreadLocal对象关联的变量副本。

  • set(T value):将指定的值设置为当前线程中与ThreadLocal对象关联的变量副本。

  • remove():删除当前线程中与ThreadLocal对象关联的变量副本。这样可以避免内存泄漏问题。注意,remove()方法只会删除当前线程中的变量副本,不会影响其他线程中的副本。

  • initialValue():当调用get()或set()方法时,如果当前线程没有与ThreadLocal对象关联的变量副本,则会调用initialValue()方法创建一个新的变量副本并与当前线程关联。默认情况下,initialValue()方法返回null,可以通过继承ThreadLocal类并重写initialValue()方法来自定义初始化值。

2.ThreadLocal原理了解吗?

从 Thread 类源代码入手。

public class Thread implements Runnable {//......//与此线程有关的ThreadLocal值。由ThreadLocal类维护ThreadLocal.ThreadLocalMap threadLocals = null;//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;//......
}

从上面 Thread 类中可以看出 Thread 类中有一个 threadLocals 和一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量。默认情况下这两个变量都是 null,只有当前线程调用 ThreadLocal 类的 set 或 get 方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap 类对应的 get、set 方法。

ThreadLocal 类的 set 方法

public void set(T value) {//获取当前请求的线程Thread t = Thread.currentThread();//取出 Thread 类内部的 threadLocals 变量(哈希表结构)ThreadLocalMap map = getMap(t);if (map != null)// 将需要存储的值放入到这个哈希表中map.set(this, value);elsecreateMap(t, value);
}
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}

最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是 ThreadLocalMap 的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread() 获取到当前线程对象后,直接通过 getMap(Thread t) 可以访问到该线程的ThreadLocalMap对象。

每个 Thread中 都具备一个 ThreadLocalMap,而 ThreadLocalMap 可以存储以 ThreadLocal 为 key ,Object 对象为 value 的键值对。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {//......
}

比如我们在同一个线程中声明了两个 ThreadLocal 对象的话, Thread内部都是使用仅有的那个ThreadLocalMap 存放数据的,ThreadLocalMap的 key 就是 ThreadLocal对象,value 就是 ThreadLocal 对象调用set方法设置的值。ThreadLocal 数据结构如下图所示。

ThreadLocalMap 是 ThreadLocal 的静态内部类。

3.ThreadLocal 内存泄露问题是怎么导致的?

内存泄漏和内存溢出的区别是什么

  • 内存泄漏指的是程序中分配的内存在不再需要时没有被正确释放或回收的情况。

  • 内存溢出指的是程序试图分配超过其可用内存的内存空间的情况。

ThreadLocal 对象和 ThreadLocalMap 中使用的 key 是弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉。但是,如果Thread 对象一直在被使用,比如在线程池中被重复使用,那么从Thread 对象到 value 的引用链就一直在,导致 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。

ThreadLocalMap 实现中已经考虑了这种情况,在调用 set、get、remove 方法的时候,会清理掉 key 为 null 的 Entry。因此,使用完 ThreadLocal 方法后最好手动调用一下 remove 方法,就可以在下一次 GC 的时候,把 key 为 null 的 Entry 清理掉。

线程池

1.什么是线程池?

线程池就是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。

2.为什么要用线程池?

池化技术想必大家已经屡见不鲜了,线程池、数据库连接池、HTTP 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

3.如何创建线程池?

方式一:通过ThreadPoolExecutor构造函数来创建(推荐)

代码示例:

ExecutorService pools = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(6),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

ExecutorService的常用方法

方式二:通过线程池的工具类 Executors 来创建。

4.为什么不推荐使用内置线程池?

因为通过 Executors 创建出来的内置线程池会让我们不够熟悉线程池的运行规则,会有资源耗尽的风险,而通过 ThreadPoolExecutor 构造函数来创建线程池能让我们更加明确线程池的运行规则,规避资源耗尽的风险。

4.线程池的参数

5.线程池的任务拒绝策略有哪些?

  • ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出 RejectedExecutionException 异常。是默认的拒绝策略。

  • ThreadPoolExecutor.DiscardPolicy:丢弃新任务,但是不抛出异常。

  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中等待最久的任务,然后把当前任务加入队列中。

  • ThreadPoolExecutor.CallerRunsPolicy: 在调用线程池的execute方法的线程中运行被拒绝的任务,从而绕过线程池直接执行。

6.线程池常用的阻塞队列有哪些?

新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在阻塞队列中。

  • ArrayBlockingQueue:是一个由数组结构组成的有界阻塞队列。它按照先进先出的原则对元素进行排序。

  • LinkedBlockingQueue:是一个由链表结构组成的有界阻塞队列。它按照先进先出的原则对元素进行排序。因为其队列大小默认为 Integer.MAX_VALUE,所以实际上它可以看作是无界队列。

  • SynchronousQueue:是一个没有缓冲的阻塞队列,每个插入操作必须等待另一个线程的移除操作,反之亦然。因此,该队列没有任何内部容量,不能预先插入元素。

  • PriorityBlockingQueue:是一个支持优先级排序的无界阻塞队列。默认情况下元素采取自然顺序排列,也可以通过实现 Comparable 接口或者在构造时传入 Comparator 对象进行排序。

由 Excutors 创建出来的内置线程池选用了不同的阻塞队列,不同的阻塞队列具有不同的特性和适用场景,具体使用哪种队列需要根据实际需求来选择。例如,

  • 如果需要控制队列大小且按照先进先出的顺序处理任务,可以选择 ArrayBlockingQueue 或 LinkedBlockingQueue;

  • 如果需要无缓冲等待两个线程之间的交互,可以选择 SynchronousQueue;

  • 如果需要按照优先级排序执行任务,可以选择 PriorityBlockingQueue。

7.线程池处理任务的流程了解吗?

  1. 如果当前运行的线程数小于核心线程数,那么就会新建一个线程来执行任务。

  2. 如果当前运行的线程数等于或大于核心线程数,但是小于最大线程数,那么就把该任务放入到任务队列里等待执行。

  3. 如果任务队列已经满了导致任务投放任务失败,但是当前运行的线程数是小于最大线程数的,就新建一个线程来执行任务。

  4. 如果当前运行的线程数已经等同于最大线程数了,新建线程将会使当前运行的线程超出最大线程数,那么当前任务会被拒绝,饱和策略会调用RejectedExecutionHandler.rejectedExecution() 方法

8.如何设定线程池的大小?

问题:很多人可能会觉得把线程池配置过大一点比较好。但是,线程数量过多的影响也是和我们分配多少人做事情一样,对于多线程这个场景来说主要是增加了上下文切换成本。到底设置多少合适可以根据具体场景分析。

什么是上下文切换

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。Linux 相比与其他操作系统有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。

CPU的8核16线程是什么意思

8核16线程指的是CPU能并行运行16线程,传统中,一个核心只能运行一个线程,但由英特尔公司开发的超线程技术硬件技术使得一个核心能并行运行多个线程。

9.如何设计一个能够根据任务的优先级来执行的线程池?

这是一个常见的面试问题,本质其实还是在考察求职者对于线程池以及阻塞队列的掌握。不同的线程池会选用不同的阻塞队列作为任务队列,比如 FixedThreadPool 使用的是LinkedBlockingQueue(无界队列),由于队列永远不会被放满,因此 FixedThreadPool 最多只能创建核心线程数的线程。ThreadPoolExecutor 的构造函数有一个 workQueue 参数可以传入任务队列。

设计方法:如果要实现一个优先级任务线程池的话,那可以考虑使用 PriorityBlockingQueue (优先级阻塞队列)作为任务队列。PriorityBlockingQueue 是一个支持优先级的无界阻塞队列,可以看作是线程安全的 PriorityQueue,两者底层都是使用小顶堆形式的二叉堆,即值最小的元素优先出队。不过,PriorityQueue 不支持阻塞操作。要想让 PriorityBlockingQueue 实现对任务的排序,传入其中的任务必须是具备排序能力的,方式有两种:

  • 提交到线程池的任务实现 Comparable 接口,并重写 compareTo 方法来指定任务之间的优先级比较规则。

  • 创建 PriorityBlockingQueue 时传入一个 Comparator 对象来指定任务之间的排序规则(推荐)。

存在的问题

  • PriorityBlockingQueue 是无界的,可能堆积大量的请求,从而导致 OOM。

  • 可能会导致饥饿问题,即低优先级的任务长时间得不到执行。

  • 由于需要对队列中的元素进行排序操作以及保证线程安全(并发控制采用的是可重入锁 ReentrantLock),因此会降低性能。

解决方法

  • 对于 OOM 这个问题的解决比较简单粗暴,就是继承PriorityBlockingQueue 并重写一下 offer 方法(入队)的逻辑,当插入的元素数量超过指定值就返回 false 。

  • 饥饿问题这个可以通过优化设计来解决(比较麻烦),比如等待时间过长的任务会被移除并重新添加到队列中,但是优先级会被提升。

  • 对于性能方面的影响,是没办法避免的,毕竟需要对任务进行排序操作。并且,对于大部分业务场景来说,这点性能影响是可以接受的。

PriorityQueue是queue系列中的一个集合

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

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

相关文章

Java EasyExcel 导入代码

Java EasyExcel 导入代码 导入方法 /*** 仓库库位导入** param req* param res* param files* throws Exception*/RequestMapping(value {"/import/line_store_locs"}, method {RequestMethod.POST})ResponseBodypublic void importStoreLoc(HttpServletRequest …

MySQL 索引、事务与存储引擎

MySQL 索引 索引的概念 索引是一个排序的列表&#xff0c;在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址&#xff08;类似于C语言的链表通过指针指向数据记录的内存地址&#xff09;。使用索引后可以不用扫描全表来定位某行的数据&#xff0c;而是先通过索引…

一种适合企业的大体量数据迁移方式

在企业进行数字化转型的过程中&#xff0c;数据迁移是一项至关重要的任务。无论是从旧系统到新系统、从本地数据中心到云端&#xff0c;还是在不同云服务提供商之间进行数据迁移&#xff0c;数据的顺利转移对业务的成功至关重要。 然而&#xff0c;随着数据体量的不断增加&…

[SWPUCTF 2021 新生赛]sql

[SWPUCTF 2021 新生赛]sql wp 输入 1 正常回显&#xff1a; ?wllm1 返回&#xff1a; Want Me? Cross the Waf Your Login name:xxx Your Password:yyy输入单引号引发报错&#xff1a; ?wllm1 返回&#xff1a; Want Me? Cross the Waf You have an error in your SQL s…

ios环境搭建_xcode安装及运行源码

目录 1 xcode 介绍 2 xcode 下载 3 xocde 运行ios源码 1 xcode 介绍 Xcode 是运行在操作系统Mac OS X上的集成开发工具&#xff08;IDE&#xff09;&#xff0c;由Apple Inc开发。Xcode是开发 macOS 和 iOS 应用程序的最快捷的方式。Xcode 具有统一的用户界面设计&#xff0…

为什么IDEA建议去掉StringBuilder,而要使用“+”拼接字符串

在字符串拼接时应该都见过下面这种提示&#xff1a; 大家普遍认知中&#xff0c;字符串拼接要用StringBuilder&#xff0c;那为什么idea会建议你是用呢&#xff0c;那到底StringBuilder和有什么具体区别呢&#xff0c;我们一起来探究一下。 普通拼接 普通的几个字符串拼接成一…

0基础学习VR全景平台篇第132篇:曝光三要素—快门速度

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 经过前面两节课的学习我们认识了曝光三要素中的感光度和光圈&#xff0c;这节课我们将一同去了解影响曝光的最后一个要素——快门速度。 (曝光三要素&#xff1a;感光度、光圈、…

YOLOv8算法优化:解决YOLOv8无法打印计算量(GFLOPs)的问题点

💡💡💡本文内容:解决YOLOv8无法打印计算量的问题点 💡💡💡本文提供:1)训练阶段自动打印计算量;2)提供离线打印计算量的代码; 1.计算量介绍 FLOPS:注意S是大写,是 “每秒所执行的浮点运算次数”(floating-point operations per second)的缩写。它常被用…

低信噪比环境下的语音端点检测

端点检测技术 是 语音信号处理 的关键技术之一为提高低信噪比环境下端点检测的准确率和稳健性&#xff0c;提出了一种非平稳噪声抑制和调制域谱减结合功率 归一化 倒谱距离的端点检测算法 1 端点检测 1-1 定义 定义&#xff1a;在 存在背景噪声 的情况下检测出 语音的起始点和…

2022年全球软件质量效能大会(QECon北京站2022)-核心PPT资料下载

一、峰会简介 当前&#xff0c;新一轮科技革命和产业变革正在重塑全球经济格局&#xff0c;以云计算为代表的新一代信息技术创新活跃&#xff0c;与实体经济深度融合&#xff0c;推动泛在连接、数据驱动、智能引领的数字经济新形式孕育而生。 新兴技术的出现给测试乃至整个软…

Vue(一):Vue 入门与 Vue 指令

Vue 01. Vue 快速上手 1.1 Vue 的基本概念 用于 构建用户界面 的 渐进性 框架 构建用户界面&#xff1a;基于数据去渲染用户看到的界面渐进式&#xff1a;不需要学习全部的语法就能完成一些功能&#xff0c;学习是循序渐进的框架&#xff1a;一套完整的项目解决方案&#x…

ftp服务器(hcia)

原理 客户端 对服务器的访问 传输和下载数据 FTP (File Transfer Protocol)–21端口 FTP传输文件有两种方法: 浏览器 软件 SFTP(Secure File Transfer Protocol)–22端口 SFTP增加了一个安全层&#xff0c;SFTP会把数据加密后进行传输&#xff0c;但更安全带来副…

VSCode 如何安装插件的历史版本

背景 在日常开发过程中&#xff0c;我们可能会遇到新版VSCode插件存在问题&#xff0c;无法正常工作的情况。这种情况下&#xff0c;一种可行的解决方案就是安装插件的历史版本。VSCode 插件默认安装的都是插件最新的版本&#xff0c;例如下面 vscode-styled-compoents 插件 本…

Kubeadmin实现k8s集群:

Kubeadmin来快速搭建一个k8s集群&#xff1a; 二进制搭建适合大集群&#xff0c;50台以上的主机&#xff0c; 但是kubeadm更适合中小企业的业务集群 环境&#xff1a; Master&#xff1a;20.0.0.71 2核4G 或者4核8G docker kubelet kubectl flannel Node1&#xff1a;20.…

ROS MoveIt!

MoveIt!是一个用于ROS的开源运动规划库&#xff0c;提供多种功能&#xff0c;包括用于运动规划的快速逆运动学分析、用于操纵的高级算法、机械手控制、动力学、控制器和运动规划。&#xff08;通过提供一个GUI来协助MoveIt!所需的各种设置&#xff0c;它允许使用RViz进行视觉反…

TCP 滑动窗口

滑动窗口&#xff08;Sliding window&#xff09;是一种流量控制技术。早期的网络通信中&#xff0c;通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况&#xff0c;同时发送数据&#xff0c;导致中间节点阻塞掉包&#xff0c;谁也发不了数据&#xff0…

RustDesk连接客户端提示key不匹配 Key Mismatch无法连接(已解决)

环境: RustDesk1.1.9 服务端docker部署 问题描述: RustDesk连接客户端提示key不匹配 Key Mismatch无法连接 解决方案: 1.docker部署RustDesk服务检查配置 networks:rustdesk-net:external: falsevolumes:hbbr:hbbs:services:hbbs:container_name: rustdesk-hbbsport…

Python入门-字符串Str

字符串 字符串 是Python中的 不可变 数据类型 1.字符串相关处理方法 大小写转换 # 大小写转换 s1HelloWorld new_s2s1.lower() print(s1,new_s2)new_s3s1.upper() print(new_s3)结果&#xff1a; D:\Python_Home\venv\Scripts\python.exe D:\Python_Home\chap6\示例6-1字符…

C#高级 01.Net多线程

一.基本概念 1.什么是线程&#xff1f; 线程是操作系统中能独立运行的最小单位&#xff0c;也是程序中能并发执行的一段指令序列线程是进程的一部分&#xff0c;一个进程可以包含多个线程&#xff0c;这些线程共享进程资源进程有线程入口&#xff0c;也可以创建更多的线程 2.…

关于Zoom ZTP和AudioCodes Ltd桌面电话缺陷暴露,导致用户遭受窃听的动态情报

一、基本内容 近期SySS安全研究员发布分析报告显示&#xff0c;Zoom的零接触&#xff08;ZTP&#xff09;和AudioCodes Ltd桌面电话配置功能中发现高危漏洞&#xff0c;可以获得对设备的完全远程控制并不受限制的访问可以被武器化&#xff0c;以窃听房间或电话、通过设备并攻击…