多线程高级知识点

多线程高级知识点

1.ThreadLocal

1.1 什么是 ThreadLocal?

​ ThreadLocal 叫做本地线程变量,意思是说,ThreadLocal 中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal 为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。

图片描述

public class ThreadLocalTest {static ThreadLocal<String> t = new ThreadLocal<>();static void print(String str) {//打印当前线程中本地内存中本地变量的值System.out.println(str + " :" + t.get());//清除本地内存中的本地变量t.remove();}public static void main(String[] args) {Thread t1  = new Thread(new Runnable() {@Overridepublic void run() {//设置线程 1 中本地变量的值t.set("t1");//调用打印方法print("thread1");//打印本地变量System.out.println("after remove : " + t.get());}});Thread t2  = new Thread(new Runnable() {@Overridepublic void run() {//设置线程 1 中本地变量的值t.set("t2");//调用打印方法print("thread2");//打印本地变量System.out.println("after remove : " + t.get());}});t1.start();t2.start();}
}

图片描述

1.2ThreadLocal 原理

​ ThreadLocal 类提供的几个方法:

public T get() { }//取值
public void set(T value) { }//设值
public void remove() { }//删除值
protected T initialValue() { }//初始化值默认返回 null,如果想在 get 之前不需要调用 set 就能正常访问的话,必须重写 initialValue() 方法。

​ ThreadLocal 类中的 set 方法和 getMap 方法:

/*** Sets the current thread's copy of this thread-local variable* to the specified value.  Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of*        this thread-local.*/
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}

​ 我们可以发现调用 ThreadLocal 的 set 方法时,传入的参数 value 会存入到一个 ThreadLocalMap 对象中。接着,我们找找 ThreadLocalMap 是从哪里来的,通过 getMap 方法,我们也不难发现 ThreadLocalMap 对象,就是当前线程的一个成员变量 threadLocals。

​ 也就是说,每次我们每次往 ThreadLocal 中 set 值就是存入了当前线程对象的 threadLocals 属性里,而 threadLocals 的类型是 ThreadLocalMap。ThreadLocalMap 可以理解为 ThreadLocal 类实现的定制化的 HashMap。

​ 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) {T result = (T)e.value;return result;}}return setInitialValue();
}

​ 同 set 方法一样,也是先根据当前线程获取 ThreadLocalMap 对象,然后在 map 中取值。

图片描述

1.3 有哪些应用场景?

  • 在进行对象跨层传递的时候,使用 ThreadLocal 可以避免多次传递,打破层次间的约束。
  • 线程间数据隔离
  • 进行事务操作,用于存储线程事务信息。
  • 数据库连接,Session 会话管理。

1.4 ThreadLocal 是否会内存泄漏?

​ Java 中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露。

​ 当仅仅只有 ThreadLocalMap 中的 Entry 的 key 指向 ThreadLocal 的时候,ThreadLocal 会进行回收的!!!

​ ThreadLocal 被垃圾回收后,在 ThreadLocalMap 里对应的 Entry 的键值会变成 null,但是 Entry 是强引用,那么 Entry 里面存储的 Object,并没有办法进行回收,所以有内存泄漏的风险。

弱引用也是用来描述非必需对象的,当 JVM 进行垃圾回收时,无论内存是否充足,该对象仅仅被弱引用关联,那么就会被回收。

2. 线程池

2.1 什么是线程池?

​ 线程池,本质上是一种对象池,用于管理线程资源。 在任务执行前,需要从线程池中拿出线程来执行。 在任务执行完成之后,需要把线程放回线程池。 通过线程的这种反复利用机制,可以有效地避免直接创建线程所带来的坏处。

2.2 为什么使用线程池?

  • 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度,当任务到达时,任务可以不需要等到线程创建就立即执行。
  • 提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以统一分配。

2.3 线程池实现原理

图片描述

​ 通过上图,我们看到了线程池的主要处理流程。我们的关注点在于,任务提交之后是怎么执行的。大致如下:

  1. 判断核心线程池是否已满,如果不是,则创建线程执行任务
  2. 如果核心线程池满了,判断队列是否满了,如果队列没满,将任务放在队列中
  3. 如果队列满了,则判断线程池是否已满,如果没满,创建线程执行任务
  4. 如果线程池也满了,则按照拒绝策略对任务进行处理。

2.4 拒绝策略

​ jdk 自带 4 种拒绝策略:

  1. CallerRunsPolicy :当任务添加到线程池中被拒绝时,会在线程池当前正在运行的 Thread 线程池中处理被拒绝的任务。
  2. AbortPolicy : 当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。
  3. DiscardPolicy :当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。JDK 默认策略。
  4. DiscardOldestPolicy :当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。

​ 这四种策略各有优劣,比较常用的是DiscardPolicy,但是这种策略有一个弊端就是任务执行的轨迹不会被记录下来。所以,我们往往需要实现自定义的拒绝策略, 通过实现RejectedExecutionHandler接口的方式。

2.5 execute() 和 submit() 区别?

​ execute()和 submit() 的区别主要两点:

  • execute()方法只能执行 Runnable 类型的任务。submit () 方法可以执行 Runnable 和 ca11ab1e 类型的任务。
  • submit()方法可以返回持有计算结果的 Future 对象,同时还可以抛出异常,而 execute() 方法不可以。

换句话说就是,execute()方法用于提交不需要返回值的任务, submit ()方法用于需要提交返回值的 任务。

2.6 shutdown() 和 shutdownNow() 区别?

  • shutdown()会将线程池状态置为SHUTDOWN,不再接受新的任务,同时会等待线程池中已有的任务执行完成再结束。
  • shutdownNow()会将线程池状态置为SHUTDOWN,对所有线程执行interrupt()操作,清空队列,并将队列中的任务返回回来。

3. volatile

3.1 volatile 的作用

​ 一般作于变量,在多处理器开发的过程中保证了内存的可见性,适用于一写多读的场景。相比于 synchronized 关键字,volatile 关键字的执行成本更低,效率更高。

3.2 volatile 的特性

​ 并发编程的三大特性为可见性、有序性和原子性。通常来讲 volatile 可以保证可见性和有序性。

  • 可见性:volatile 可以保证不同线程对共享变量进行操作时的可见性。即当一个线程修改了共享变量时,另一个线程可以读取到共享变量被修改后的值。
  • 有序性:volatile 会通过禁止指令重排序进而保证有序性。
  • 原子性:对于单个的 volatile 修饰的变量的读写是可以保证原子性的,但对于 i++ 这种复合操作并不能保证原子性。这句话的意思基本上就是说 volatile 不具备原子性了。

3.3 volatile 和 synchronized 区别?

  • volatile 是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取,synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法和类级别的
  • volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性
  • volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
  • volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化

4. 常见锁

4.1 乐观锁 VS 悲观锁

​ 乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度。

​ 悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。

​ 乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。

悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。

乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

4.2 自旋锁 VS 适应性自旋锁

​ 阻塞或唤醒一个 Java 线程需要操作系统切换 CPU 状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。

​ 在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃 CPU 的执行时间,看看持有锁的线程是否很快就会释放锁。

​ 而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁

​ 自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是 10 次,可以使用-XX:PreBlockSpin 来更改)没有成功获得锁,就应当挂起线程。

​ 自适应的自旋锁(适应性自旋锁)自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

4.3 可重入锁 VS 非可重入锁

​ 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者 class),不会因为之前已经获取过还没释放而阻塞。

​ 非可重入锁,则与上面相反,会线程出现死锁,整个等待队列中的所有线程都无法被唤醒。

4.4 独享锁 VS 共享锁

​ 独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程 T 对数据 A 加上排它锁后,则其他线程不能再对 A 加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。

​ 共享锁是指该锁可被多个线程所持有。如果线程 T 对数据 A 加上共享锁后,则其他线程只能对 A 再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。

4.5 公平锁 VS 非公平锁

​ 公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU 唤醒阻塞线程的开销比非公平锁大。

​ 非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU 不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

5. 线程间通信

5.1 线程间通信

​ Java 线程通信是将多个独立的线程个体进行关联处理,使得线程与线程之间能进行相互通信。比如线程 A 修改了对象的值,然后通知给线程 B,使线程 B 能够知道线程 A 修改的值,这就是线程通信。

5.2 wait/notify 机制

​ 一个线程调用 Object 的 wait() 方法,使其线程被阻塞;另一线程调用 Object 的 notify()/notifyAll() 方法,wait() 阻塞的线程继续执行。

  • wait():让当前线程释放对象锁并进入等待(阻塞)状态。
  • notify():唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到 CPU 的执行。
  • notifyAll():唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到 CPU 的执行。

wait()、notify() 和 notifyAll() 三个方法来实现,这三个方法均非 Thread 类中所声明的方法,而是 Object 类中声明的方法。原因是每个对象都拥有 monitor(锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作,而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。

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

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

相关文章

服务器运维管理面板1Panel本地部署并结合内网穿透实现远程访问

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

EtherCAT主站SOEM -- 13 --Qt-Soem通过界面按键控制 EtherCAT IO模块的io输出

EtherCAT主站SOEM -- 13 --Qt-Soem通过界面按键控制 EtherCAT IO模块的io输出 一 mainwindow.c 文件函数:1.1 自定义PDO配置2.2 主站初始化2.3 去motrorcontrol界面二 motrorcontrol.c 文件三 allvalue.h 文件该文档修改记录:总结一 mainwindow.c 文件函数: mainwindow主界…

Docker学习与应用(四)-容器数据卷

1、容器数据卷 1&#xff09;什么是容器数据卷 docker的理念回顾 将应用和环境打包成一个镜像&#xff01; 数据&#xff1f;如果数据都在容器中&#xff0c;那么我们容器删除&#xff0c;数据就会丢失&#xff01;需求&#xff1a;数据可以持久化 MySQL&#xff0c;容器删…

MYSQL 存储过程/存储函数

简而言之&#xff0c;类似于封装函数 特点 基本语法 create peocedure p1() begin select coun(*) from studuent; end; call p1(); 设置完别忘了把delimiter改回来 变量 系统变量 用户自定义变量 set myname its; set myage : 10; 局部变量 if 参数&#xff08;IN&…

Oracle VM VirtualBox xx needs the Micrsoft Visual C++ 2019错误

错误展示 解决方法 重修安装 Visual C 文件 1、前往官网 C 中 Windows 编程概述 | Microsoft Learn 2、找到对应的包 左边导航栏依次选择&#xff1a; 部署本机桌面应用程序-----重新分发Visual C 文件-----最新受支持的Visual C可再发型程序包下载 根据自己电脑系统进行选…

linux mv command and authority managemet

mv abbreviation for move, as to move files and folders or change the folder name/ rename the specific folderLinux常见权限解释 -rw------- (600) 只有拥有者有读写权限。 -rw-r–r-- (644) 只有拥有者有读写权限&#xff1b;而属组用户和其他用户只有读权限。 -rwx--…

私域运营常用的ChatGPT通用提示词模板

私域流量获取&#xff1a;如何获取更多的私域流量&#xff0c;提高用户粘性和转化率&#xff1f; 私域用户画像&#xff1a;如何建立私域用户的画像&#xff0c;了解用户需求和行为习惯&#xff1f; 私域内容运营&#xff1a;如何制定有效的私域内容策略&#xff0c;提高用户…

mybatis和Hibernate对比

MyBatis和Hibernate是两个在Java开发中常用的持久化框架&#xff0c;它们有以下区别&#xff1a; 1. ORM vs. SQL映射&#xff1a; - Hibernate是一个全功能的ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;它将Java对象与数据库表之间的映射关系进行管理&#xff0…

Python 教程 02:Python 编程环境的搭建与 IDE 的选择

目录 一、搭建 Python 环境 1.1 Python 官网 1.2 下载 Python 1.2.1 选择版本 1.2.2 选择平台 1.2.3 下载安装文件&#xff08;Windows & macOS&#xff09; 1.3 安装环境 1.3.1 Windows 平台 1.3.2 macOS 平台 1.3.3 Linux 平台 1.4 验证安装是否成功 二、选择…

小游戏实战丨基于PyGame的贪吃蛇小游戏

文章目录 写在前面PyGame贪吃蛇注意事项系列文章写在后面 写在前面 本期内容&#xff1a;基于pygame的贪吃蛇小游戏 下载地址&#xff1a;https://download.csdn.net/download/m0_68111267/88700188 实验环境 python3.11及以上pycharmpygame 安装pygame的命令&#xff1a;…

Jedis快速入门

Jedis快速入门 1.Jedis使用的基本步骤&#xff1a;1.1 引入依赖1.2 创建Jedis对象&#xff0c;建立连接1.3 使用Jedis&#xff0c;方法名与Redis命令一致1.4 释放资源1.5 完整代码1.6 可视化工具查看 2.Jedis的连接池2.1 代码2.2 获取连接修改如下 1.Jedis使用的基本步骤&#…

Mac 升级ruby 升级brew update

Mac 自身版本是2.x 查看ruby版本号 打开终端 ruby -v 1.brew update 如果报错 这时候brew更新出问题了 fatal: the remote end hung up unexpectedly fatal: early EOF fatal: index-pack failed error: RPC failed; curl 18 HTTP/2 stream 3 was reset fatal: th…

Activity启动流程

早就想写这个笔记用于记录这段知识&#xff0c;但是碍于太过庞大所以始终没有进行这段知识的整理 很多博客喜欢画一个时序图展示所有的流程&#xff0c;但是过于庞大&#xff0c;看起来有点吃力&#xff0c;这里我们画多个时序图来展示这个流程 1.app请求AMS启动Activity 在前…

在网址URL中隐藏数据的一些方案

隐藏 ID 比如数据库主键自增ID&#xff0c; UUID&#xff0c;MongoDB 的ObjectId等&#xff0c;你不希望用户看到。可以使用sqids方案。 Sqids (pronounced “squids”) is a small library that lets you generate unique IDs from numbers. It’s good for link shortening,…

理解JavaScript事件循环机制

JavaScript作为前端开发的核心语言之一&#xff0c;其事件循环机制是实现异步编程的关键。本文将深入探讨JavaScript事件循环机制&#xff0c;帮助您更好地理解它是如何工作的&#xff0c;以及如何在前端开发中充分利用这一机制。 1. 什么是事件循环&#xff1f; JavaScript是…

实现pytorch版的mobileNetV1

mobileNet具体细节&#xff0c;在前面已做了分析记录&#xff1a;轻量化网络-MobileNet系列-CSDN博客 这里是根据网络结构&#xff0c;搭建模型&#xff0c;用于图像分类任务。 1. 网络结构和基本组件 2. 搭建组件 &#xff08;1&#xff09;普通的卷积组件&#xff1a;CBL …

Mjdjourney使用手册

Mjdjoureny后缀解析 --ar 宽高比设置 --ar 2:3,--ar 1:1,--ar 16:9 --c 多样性设置&#xff08;风格差异&#xff09;范围0-100&#xff0c;默认0&#xff0c;数值越大图片风格差异越大 --s 风格化设置 范围0-100&#xff0c;默认50&#xff0c;数字越大mjdj就有更大的发挥…

React Portals

简介 React Portal 可以将组件渲染到dom树的不同位置&#xff0c;同时可以渲染到任意父级元素&#xff0c;可以实现漂浮层功能。 使用样例 本篇文章通过React Portals实现对话框&#xff0c;下面将会给出具体实现。 protal组件 Portal.jsx import {useState} from "re…

学习笔记——C++运算符之赋值运算符

上次我们说到C的运算符共有四种&#xff0c;分别是算术运算符&#xff0c;赋值运算符&#xff0c;比较运算符和逻辑运算符 &#xff0c;下面介绍赋值运算符&#xff0c;赋值运算符主要的种类及作用如下表所示。 #include<bits/stdc.h> using namespace std; int main(){…

听GPT 讲Rust源代码--compiler(30)

File: rust/compiler/rustc_const_eval/src/transform/promote_consts.rs 在Rust的编译器源代码中&#xff0c;rust/compiler/rustc_const_eval/src/transform/promote_consts.rs文件的作用是执行常量传播和优化的转换过程。 该文件中的PromoteTemps结构体是一个转换器&#xf…