Java 多线程知识

news2024/10/6 16:24:24

参考链接:https://www.cnblogs.com/kingsleylam/p/6014441.html
https://blog.csdn.net/ly0724ok/article/details/117030234/
https://blog.csdn.net/jiayibingdong/article/details/124674922

导致Java线程安全问题最主要的原因:
(1)多线程同时访问共享数据。
(2)多线程操作共享数据的过程中使用的计算方法不具备原子性。
解决线程安全问题的方案:
(1)避免共享数据。
(2)确保使用共享数据的原子性。
避免数据共享:
JAVA虚拟机在内存管理过程中将内存划分为不同区域,其中类成员变量存储在堆内存,方法变量存储在栈内存。堆内存在不同线程之间共享数据,有线程安全问题,而栈内存是线程独占的内存,不存在线程安全线问题。在允许的情况下,不使用成员变量、而是用方法变量、临时变量的话,可以避免共享数据,从而确保数据的线程安全问题。

public class A
{
	private int account = 0;
	public void cal()
	{
		for(int i=0;i<100;i++)
		{
			acount++;
		}
	}
	public int getAccount()
	{
		return account;
	}
}

多线程并发的情况下,account 有线程安全问题,而变量i是线程安全的、没有线程安全问题。
JMM内存模型: 描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
在Java内存模型中:
(1)所有变量都存储在主内存中
(2)每个线程都有自己独立的工作内存,里面保存该线程使用到的变量副本。(主内存中该变量的一份拷贝)
(3)线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中进行读写。
(4)线程之间无法访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
共享变量实现可见性,必须经过如下两个步骤:
(1)线程中工作内存中的共享变量如果更新,需要将更新过后共享变量刷新到主内存中。
(2)主内存将最新的共享变量更新到其他工作内存中。
在这里插入图片描述
上图体现出线程只能与工作内存交互,不能直接访问主内存。当主内存中有一个共享变量X,则工作内存将X拷贝,线程操作工作内存中的X副本。

共享变量: 一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
原子性: 指一个操作不可被中断,要么全部执行成功,要么全部执行失败。同一时刻只能有一个线程对它进行操作。
可见性: 一个线程对共享变量值的修改能够及时被其他线程看到。

指令重排序:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中的各个语句的执行先后顺序同代码中的顺序一致,但它会保证程序最终执行结果和代码顺序执行的结果是一致的。
内存屏障: 用来禁止指令重排序。内存屏障分为两种:Load Barrier 和 Store Barrier,即读屏障和写屏障。作用: (1)阻止屏障两侧的指令重排序,即屏障下面的代码不能和屏障上面的代码交换顺序。(2)在有内存屏障的地方,线程修改完共享变量以后会马上把该变量从本地内存写回到主内存,并且让其他线程本地内存中该变量副本失效。
对于Load Barrier来说,在指令前插入
Load Barrier
,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,其他线程程可见

synchrnized

具有 原子性、可见性

对象锁(monitor)机制

进入同步代码块之前首先执行monitorenter指令,退出同步代码块执行monitorexit指令。当线程获得monitor后才能继续往下执行,否则就只能等待。
任何一个对象都有一个自己的monitor,线程执行对象的同步方法或同步块时,执行方法的线程必须获取该对象的monitor才能进入同步块和同步方法,若获取不到,则进入阻塞状态,进入同步队列。线程释放锁的时候会将值刷新到主内存中,其他线程获取锁时会强制从主内存中获取最新的值。——happen-before。类似线程通信。
**锁的重入性:**在同一锁程中,线程不需要再次获取同一把锁。synchrnized先天具有重入性。每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加1,释放锁后就会将计数器减1。

CAS操作

悲观锁: 假设每一次执行临界区代码都会发生冲突,所以当前线程获取锁的过程会阻塞其他线程获取该锁。
乐观锁: 假设每一次执行临界区代码都不会发生冲突,所以不会阻塞其他线程操作。因此线程就不会出现阻塞停顿的状态。
CAS是用来鉴别乐观锁是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。

CAS操作过程

CAS包含三个值,分别为:V内存地址存放的实际值、O预期的值、N更新的新值。当V和O相同时,表明该值没有被其他线程更改过,可以把新值N赋值给V。相反,V和O不相同,表明该值已经被其他线程改过了,则该旧值不是最新版本的值了,所以不能将新值N赋值给V,返回V即可。当多个线程使用CAS操作一个变量时,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试,当然也可以选择挂起线程。

CAS的问题

**ABA问题:**因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C。
**自旋时间过长:**使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。
**只能保证一个共享变量的原子操作:**当对一个共享变量执行操作时CAS能保证其原子性,如果对多个共享变量进行操作,CAS就不能保证其原子性。有一个解决方案是利用对象整合多个共享变量,即一个类中的成员变量就是这几个共享变量。然后将这个对象做CAS操作就可以保证其原子性。atomic中提供了AtomicReference来保证引用对象之间的原子性。

voliate

轻量级同步机制。
voliate 是java中的一个关键字,用于修饰变量,被修饰的变量标记为线程共享,编译与运行时都会检查该变量,不会对其进行重排序。保证可见性有序性,不保证原子性
被voliate修饰的变量在编译成字节码时会多个lock前缀指令,该指令在执行过程中生成一个内存屏障,保证充排序后的指令不会越过内存屏障。即volatile之前的代码只会在volatile之前执行,volatile之后的代码只会在volatile之后执行。
满足以下一点,volatile 修饰的共享变量不加锁也能保证线程安全: 运算结果不依赖共享变量的当前值。(i++反例)。

Semaphore

继承关系
在这里插入图片描述
Semaphore用于管理信号量,在并发编程中可以控制访问同步代码的线程数量。

线程池

线程池是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后再创建线程后自动启动这些任务。
熟知的线程池: JDBC、数据库连接池(DataSource)、String(字符串常量池)

线程池的作用

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

线程池的基本原理

一个池子里面有很多线程,当再次需要执行某个任务时,不需要再创建线程了,而是直接从线程池中取出一个现成的线程供使用,即使该线程完成了任务,也不销毁线程,而是继续呆在线程池里准备迎接下一个任务。

为什么从池子中取要比创建线程快?

创建线程是在操作系统内核中完成的,涉及从用户态向内核态切换的操作,这个操作需要一定的开销。应用程序创建线程的是需要通过系统调用来完成的,进入操作系统内核中执行,也就是说,线程本质上就是PCB,是内核中的数据结构。创建线程是在内核中完成的,需要经历用户态->内核态的转变,而从线程池中取线程,把线程放回线程池,这一套是纯用户态的逻辑。

线程池主要参数

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
							  ThreadFactory threadFactory,
                              
                              RejectedExecutionHandler handler)
参数解释
corePoolSize线程池中的核心线程数。即使这些线程处于空闲状态也不会被销毁。任务提交到线程池后,首先会检查当前线程数是否达到了corePoolSIze,如果没有达到的话,会创建一个新线程来处理这个任务。
maximumPoolSize线程池允许的最大线程个数;当线程数达到corePoolSize后,如果有任务继续提交到线程池,会将任务缓存到工作队列中。如果队列也已满,则会创建一个新线程来进行处理。线程池不会无限制地去创建新线程,它会有一个最大线程数限制,这个数量由maximumPoolSize限制。
keepAliveTime空闲线程存活时间。一个线程如果处于空闲状态,并且当前线程数大于corePoolSize ,那么在keepAliveTime 后,这个空闲线程会被销毁。
unit空闲线程存活时间单位,keepAliveTime 的计量单位。
workQueue工作队列。新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk提供了四种工作队列:ArrayBlockingQueue、LinkedBlockingQuene、SynchronousQuene、PriorityBlockingQueue。
threadFactory创建一个新线程时使用的工厂,可以用来设定线程名,是否为daemon线程等等。
headler拒绝策略。当工作队列中的任务已经到达最大限制且线程池中的线程数也达到最大限制,这时有新任务再次提交会触发拒绝策略。jdk提供了4种拒绝策略:CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy。

**workQueue工作队列 **

  1. ArrayBlockingQueue 基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
  2. LinkedBlockingQuene 基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
  3. SynchronousQuene 一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
  4. PriorityBlockingQueue 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

handler 拒绝策略

  1. CallerRunsPolicy 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
  2. AbortPolicy 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
  3. DiscardPolicy 该策略下,直接丢弃任务,什么都不做。
  4. DiscardOldestPolicy 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。

线程池如何确定线程数量

在拥有N个处理器的系统上,如何确定线程数量。需要确定任务是CPU密集型应用还是IO密集型应用,还是混合型应用。如果是CPU密集型应用,则线程池大小设置为N+1。如果是IO密集型应用,则线程池大小设置为2N+1。
实际开发处理方案是需要实验验证的。针对自己的程序进行性能测试,对线程池设置不同的数目:0.5N、N、1.5N、2N… 然后分别记录每种情况下程序的一些核心性能指标和系统负载情况,最后选择一个合适的配置。

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

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

相关文章

修改亮度、对比度、色调、饱和度,达到预期效果

用户态可以通过v4l2自带工具进行一些UVC相机参数的设定&#xff0c;包括采集卡驱动之类&#xff0c;也可以通过v4l2自带工具进行参数设定。 通过修改这些参数的值&#xff0c;可以弥补相机本身彩色部分自带不足。 总的来说就这么几种命令&#xff1a; 查看设备所有参数信息&a…

C++系列三:变量、常量

常量、变量 1. 变量1.1 定义变量1.2 初始化变量1.3 变量数据类型1.4 变量作用域 2. 常量2.1 定义常量2.2 常量类型2.3 常量作用域2.4 常量用法 3. 总结 1. 变量 变量是一个用于存储值的命名内存位置&#xff0c;可以存储多种不同类型的数据&#xff0c;例如整数、实数、字符或…

淘宝搜广推技术备注

第一篇文章 一、序列特征处理方式 1&#xff1a;淘宝的类目体系中&#xff0c;有大类目、小类目&#xff08;淘宝大类目和小类目怎么区分&#xff1f;有何运营技巧&#xff1f;-卖家网&#xff09;&#xff0c;在做推荐系统时&#xff0c;有个sim建模&#xff08;search-base…

【Java】抽象类接口

目录 1.抽象类 2.接口 2.1实现多个接口 2.2接口之间的关系 2.3接口使用实例 2.3.1Comparable接口 2.3.2Comparator接口 2.3.2Clone接口 2.4抽象类与接口的区别 1.抽象类 定义&#xff1a;抽象方法&#xff1a;这个方法没有具体的实现&#xff1b; 抽象类&#xff1a;不…

边学边记——数据结构☞和搜索有关的数据结构(搜索树,Set,Map,哈希表)

目录 模型 一、搜索 1. 场景 2. 搜索树 2.1 概念 2.2 查找 2.3 插入 2.4 删除 2.5 实现 2.6 性能分析 2.7 和Java的关系 二、Set 1. 常见方法 2. 注意 三、Map 1. 关于Map.Entry的说明,> 2. Map的常用方法说明 3. 注意 四、哈希表 1. 概念 2. 冲突 2…

[论文分享] VOS: Learning What You Don‘t Know by Virtual Outlier Synthesis

这篇文章是ICLR‘ 2022的一篇文章。 No.contentPAPER{ICLR’ 2022} VOS: Learning What You Don’t Know by Virtual Outlier SynthesisURL论文地址CODE代码地址 Motivation 现有OOD Detection方法大多依赖于真实的离群点数据集进行模型正则化&#xff0c;实际应用中过于昂…

常用位运算

一、求解二进制表示的第k位数字 #include<iostream> using namespace std;int main() {int n 10; // 例如&#xff0c;十进制10用二进制表示为1010 for(int k3;k>0;k--)cout << (n >> k & 1);// 第一步&#xff1a;右移k位// 第二步&#xff1a; &am…

JVM基础总结

文章目录 一、程序计数器二、Java虚拟机栈栈内存溢出【StackOverflowError】线程运行诊断 三、本地方法栈【Native Method Stacks】四、堆【Head】线程共享堆内存溢出【OutOfMemoryError&#xff1a;Java heap space】堆内存诊断 五、方法区【Method Area】线程共享运行时常量池…

Springboot +Flowable,为流程设置租户

一.简介 什么叫flowable的租户呢&#xff1f;这边举个例子&#xff1a; 假设现在有 A、B、C、D 四个子系统&#xff0c;四个子系统都要部署同一个名为 leave 的流程&#xff0c;如何区分四个不同子系统的的流程呢&#xff1f;通过租户就可以解决这个问题。Flowable 中的租户其…

dell r750服务器安装centos系统全记录

1、启动盘制作 1.1 下载系统 打开 https://www.centos.org/download/ 任意选择一个镜像网站&#xff0c;博主选择的是163镜像 下载内存为4g镜像文件 这里也可以参考 https://blog.csdn.net/weixin_46703995/article/details/121191113 1.2 下载启动盘制作软件 linux系统一…

Denoising Diffusion Probabilistic Model,DDPM阅读笔记——(二)

Denoising Diffusion Probabilistic Model&#xff0c;DDPM阅读笔记 一、去噪扩散概率模型&#xff08;Denoising Diffusion Probabilistic Model&#xff0c;DDPM&#xff09; 一、去噪扩散概率模型&#xff08;Denoising Diffusion Probabilistic Model&#xff0c;DDPM&…

C++数据结构:手撕红黑树

目录 一. 红黑树的概念及结构 二. 红黑树节点的定义 三. 红黑树节点的插入 3.1 初步查找插入节点的位置并插入节点 3.2 红黑树结构的调整 3.3 红黑树节点插入完整版代码 四. 红黑树的结构检查 4.1 检查是否为搜索树 4.2 检查节点颜色是否满足要求 附录&#xff1a;红黑…

TypeScript进阶

目录 TypeScript 与 Vue 文档说明 vscode 插件说明 准备页面基本结构 defineProps与Typescript defineEmits与Typescript ref与Typescript reactive与Typescript computed与Typescript 事件对象与Typescript 模板Ref与Typescript 可选链操作符和非空断言 TypeScript…

21.网络爬虫—js逆向详讲与实战

网络爬虫—js逆向 js逆向JavaScript逆向的详细讲解实战演示有道翻译设置密钥和初始向量对密钥和初始向量进行哈希处理创建AES对象并解密消息移除padding并返回结果 前言&#xff1a; &#x1f3d8;️&#x1f3d8;️个人简介&#xff1a;以山河作礼。 &#x1f396;️&#x1f…

python基于卷积神经网络实现自定义数据集训练与测试

样本取自岩心照片&#xff0c;识别岩心是最基础的地质工作&#xff0c;如果用机器来划分岩心类型则会大大削减工作量。 下面叙述中0指代Anhydrite_rock&#xff08;膏岩&#xff09;&#xff0c;1指代Limestone&#xff08;灰岩&#xff09;&#xff0c;2指代Gray Anhydrite_r…

深度学习-第T6周——好莱坞明星识别

深度学习-第T6周——好莱坞明星识别 深度学习-第T6周——好莱坞明星识别一、前言二、我的环境三、前期工作1、导入数据集2、查看图片数目3、查看数据 四、数据预处理1、 加载数据1、设置图片格式2、划分训练集3、划分验证集4、查看标签 2、数据可视化3、检查数据4、配置数据集 …

Flutter学习之旅 - 页面布局Stack层叠组件

文章目录 StackPositioned定位布局浮动导航(StackPositioned)FlutterMediaQuery获取屏幕宽度和高度StackAlign Stack Stack意思是堆的意思&#xff0c;我们可以用Stack结合Align或者Stack结合Positioned来实现页面的定位布局 属性说明alignment配置所有元素显示位置children子组…

23.Lambda表达式

Lambda表达式 一、Lambda表达式背景 Lambda 表达式(lambda expression)是一个匿名函数&#xff0c;Lambda表达式基于数学中的λ演算得名&#xff0c;直接对应于其中的lambda抽象(lambda abstraction)&#xff0c;是一个匿名函数&#xff0c;即没有函数名的函数。Lambda表达式…

2023-05-05 背包问题

背包问题 1 01背包和完全背包问题 01背包问题 有N件物品和一个容量为V的背包&#xff0c;第i件物品的体积是v[i]、价值是w[i]&#xff0c;每种物品只可以使用一次&#xff0c;求将哪些物品放入背包可以使得价值总和最大。这里的w是weight即权重的意思 这是最基础的背包问题&a…

【飞书ChatGPT机器人】飞书接入ChatGPT,打造智能问答助手

文章目录 前言环境列表视频教程1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 转载自远控源码文章&#xff1a;飞书接入ChatGPT - 将ChatGPT集…