当前位置: 首页 > news >正文

并发编程(八股)

概述

并行:同一个时间点,多个线程同时执行
并发:同一个时间段,多个线程交替执行,微观上是一个一个的执行,宏观上感觉是同时执行
核心问题:
多线程访问共享数据存在资源竞用问题
不可见性
java内存模型(jmm)
变量数据都存在于主内存里,每个线程还有自己的工作内存(本地内存),规定线程不能直接对主内存中的数据进行操作,只能把主内存的数据加载到自己的工作内存中操作,操作完成后,在写会主内存
这样的设计就引发了不可见性,应该线程在自己的工作内存中操作了数据后,另一个线程也在操作,但是不知道数据已经被另一个线程修改了
乱序性
为了优化指令执行,在执行一些等待时间比较长的执行时,可以把其他的一些执行指令提前执行,提高速度
但是在多线程场景下,就会出现问题
非原子性
线程切换执行带来非原子性
cpu保证原子性执行cpu指令级别的,但是对于高级语言的一条代码,有时是要拆分为多条指令的,线程在执行到某条执行时操作系统会切换到其他线程去执行,这样这条高级语言指令执行就是非原子性的
总结:工作内存的缓存导致了不可见性,指令的优化导致了无序性,线程的切换执行导致了非原子性

volatile关键字

所修饰的变量在一个工作内存中操作后,底层会将工作内存中的数据同步到其他工作内存中,使其立即可见

  1. 确保多线程操作变量时的可见性,当一个线程修改了变量值,新值会立即对其他线程可见
  2. 防止指令重排序的发生
  3. volatile 无法保证变量操作的原子性

如何保证原子性?

解决非原子性问题,都可以通过加锁的方式实现,synchronized和ReentrantLock都可以实现
Java还提供了一种方案,在不加锁的情况下,实现++操作的原子性就是原子类
在java.util.concurrent包下,定义了许多与并发编程相关的处理类,此包一般大家简称为JUC
实现方式:采用CAS(比较并交换)思想,当多个线程对同一个内存数据操作时,假设A线程把主内存数据加载到自己的工作内存中,这个工作内存中的值就是预期值,然后在自己的工作内存中操作后,当写回主内存时,先判断预期值和主内存的值是否一致,如果一致说明还没有其他线程修改,直接写回主内存,一旦预期值和主内存的值不一样,说明有其他线程已经修改过了,线程A需要重新获取主内存的值,重新操作,判断,直到预期值和主内存中的值相同才结束,否则一直判断
由于采用自旋的方式,使得线程都不会阻塞,一直自旋,适合并发量低的场景,如果并发量过大,线程会一直自旋,会导致CPU开销大
还会有一个ABA问题,线程A拿到主内存值后,期间有其他线程已经多次修改内存数据,最终又修改到和线程A拿到的值相同,可以通过带版本号解决

锁分类

乐观锁/悲观锁

乐观锁是一种不加锁的实现,例如原子类,认为不加锁,采用自旋的方式尝试修改共享数据是不会有问题的,悲观锁是一种加锁的实现,例如synchronized和ReentrantLock认为不加锁修改共享数据会出问题

可重入锁

又名递归锁,当同一个线程,获取锁进入到外层方法中,可以在内存中可以进入另一个方法中(内层与外层使用同一把锁)
synchronized和ReentrantLock也是可重入锁

读写锁

ReentrantReadWriteLock读写锁实现
ReentrantReadWriteLockWriteLock()
ReentrantReadWriteLock.ReadLock()
读读不互斥
读写互斥
写写互斥

分段锁

将锁的粒度进一步细化,提高并发效率
hashtable是线程安全的,方法上都加了锁,假如有两个线程同时读,也只能一个一个的读,并发效率低
concurrentHashMap没有给方法上加锁,使用hashtable表中的每个位置上的第一个对象作为锁对象,这样可以多个线程对不同位置进行操作,相互不影响,只有对同一个位置操作时,才互斥
有多吧锁,提高并发操作的效率

自旋锁

线程尝试不断的获取锁,当第一次获取不到时,线程不阻塞,尝试继续获取锁,有可能后面几次尝试后,有其他线程释放了锁,此时就可以获取锁,当尝试获取到一定次数后(默认10次),仍然获取不到任何锁,那么可以进入阻塞状态
synchronized就是自旋锁
并发量低适合自旋锁

共享锁/独占锁

共享锁:可以被多个线程共享,读写锁中的读锁就是共享锁
独占锁:一把锁只能有一个线程使用,读写锁的写锁,synronized和reentrantLock都是独占锁

公平锁/非公平锁

公平锁:可以按照请求获得锁的顺序来得到锁
非公平锁:不按照请求获取锁的顺序来得到锁
synchronized是非公平的
ReentrantLock默认是非公平的,可以通过构造方法去改变成公平锁

锁状态

无锁

偏向锁

一段同步代码块一直由一个线程执行,那么会在锁对象中记录下了线程信息,可以直接会的锁

轻量级锁

当锁状态为偏向锁时,此时又有其他线程访问,锁状态升级为轻量级锁,线程不阻塞,采用自旋方式获取锁

重量级锁

当锁状态为轻量级锁时,如果有大量的线程到来,锁状态升级为重量级锁,自旋的线程会进入阻塞状态,由操作系统去调度管理

Synchronized

关键字,由jvm提供,实现同步,隐式的加锁和释放锁修饰代码块和方法,修饰代码块时需要我们提供一个同步锁对象,任意类的对象都可以,但只能是唯一一个,记录线程有没有进入到同步代码块,修饰方法时,对象是自己提供的,非静态方法锁对象默认为this,静态方法锁对象为当前的class对象,控制同步是依靠进入和退出监视器对象实现的,如果是同步方法,在指令中会为方法添ACC_SYNCHRONIZED标志,如果是同步代码块,在进入到同步代码块时,会执行monitorenter,离开同步代码块时或出异常时,执行monitorexit,为了提高锁的获取与释放效率在对象头中记录锁状态,

AQS

抽象同步队列,是一个实现线程同步的框架,并发包中有很多底层都用到了AQS,通过 FIFO 队列 和 原子状态变量(state) 管理线程的阻塞与唤醒,提供了统一的底层实现机制
核心思想:线程竞争资源时,通过 CAS 操作尝试修改 state 获取资源,成功则直接执行;失败则封装为节点进入队列等待,通过 LockSupport 实现阻塞 / 唤醒。AQS 支持独占(如 ReentrantLock)和共享(如 Semaphore)两种模式,子类只需重写 tryAcquire/tryRelease 等方法即可快速实现自定义同步器,是构建锁、信号量等并发工具的高效底层机制

JUC常用类

ConcurrentHashMap

HashMap适合单线程,不允许多个线程同时访问操作,如果有多线程访问会报异常
Hashtable是线程安全的 直接给方法加锁,效率低
ConcurrentHashMap是线程安全的 没有直接给方法加锁,用哈希表中每一个位置上的第一个元素作为锁对象,哈希表的长度是16,那么就有16把锁对象,锁住自己的位置即可,这样如果多个线程操作不同的位置,那么相互不影响,只有多个线程操作同一个位置,才会等待
如果位置上没有何元素,那么会采用cas机制插入数据到对应位置
hashtable concurrentHashMap 键值都不能为空

CopyOnWriteArrlist

ArrayList是单线程场景下使用的,在多线程场景下会报异常
Vector 是线程安全的,在方法上加了锁,效率低,读写用的同一把锁
CopyOnWriteArrayList  写方法上加了锁(ReentrantLock实现的),写入数据时,先把圆数组做了一个备份,把要添加的数据写入到备份数组中,当写入完成后,在把修改后的数组赋值到原数组中去
给写加锁,读没有加锁,读的效率就变高了,适合写操作少,读操作多的场景

CopyOnWriteArraySet

CountDownLatch

线程池

为减少频繁的创建和销毁线程
JDk5引入了线程池,建议使用ThreadPoolExecutor类来创建线程池,提高了效率

ThreadPoolExecutor  构造器的各个参数:
corePoolSize  核心线程池的线程数量(初始化5)
maximumPoolSize  线程池中最大线程数量(初始化10)
keepAliveTime  空闲线程存活时间当核心线程池中的线程足以应付任务时,非核心线程池中的线程在指定空闲时间到期后,会销毁
unit  时间单位
workQueue  等待队列,当核心线程池中的线程都在使用时,会先将等待的线程放到队列中,如果队列满了,才会创建新的线程(非核心线程池中的线程)
threadFactory  线程工厂,用来创建线程池中的数量
handler  拒绝处理任务的策略
四大策略:AbortPolicy  抛异常
CallRunsPolicy  由提交任务的线程执行,例如在main线程提交,则由main线程执行拒绝任务
DiscardOldestPolicy  丢弃等待时间最长的任务
DiscardPolicy  丢弃最后不能执行的任务

工作流程:

有大量工作任务到来时,先判断核心线程池中的线程是否都忙着,有空闲的就让核心线程池中的线程执行任务,没有空闲的,就判断等待的队列是否已满,如果没满,就把任务添加到队列等待,如果队列已经满了,在判断非核心线程池中的线程是否都忙着,如果有空闲的,,没满,就就由非核心的线程池中的线程执行,如果非核心线程池也满了,就使用对应的拒绝策略处理

提交任务

void ececute 没有返回值
submit  有返回值

关闭线程池

shutdown  执行后不再接收任务,会把线程池中和等待队列中已有的任务执行完后再停止
shutdownNow  立即执行,等待的任务不再执行

ThreadLocal

为每一个线程提供一个变量副本,只在当前线程中使用,相互隔离

实现:为每个Thread创建一个ThreadLocal.ThreadLocalMap threadLocals把变量副本放在ThreadLocal.ThreadLocalMap 中,用ThreadLocal对象统一作为键,每一个获取变量时,先拿到当前线程,拿到线程中的ThreadLocal.ThreadLocalMap 

内存泄漏:对象已经不用了,但是垃圾回收不能回收该对象(例如数据库连接对象,流对象,socket)
不再使用时调用remove方法,删除键值对,可以避免内存泄漏问题

对象引用:
强引用
Object obj=new Object();强引用
对象如果有强引用关联,那么肯定是不能被回收的
软引用
被SoftReference类包裹的对象,当内存充足时,不会被回收,当内存不足时,即使有引用指向,也会被回收
弱引用
被WeakReference类所包裹的对象,只要发生垃圾回收,该类对象都会被回收掉
ThreadLocal被弱引用管理
当发生垃圾回收时,被回收掉,但是value还与外界保持着引用关系,
虚引用
被PhantomReference类包裹的对象,随时都可以被回收,通过虚引用对象跟踪对象回收状态

http://www.dtcms.com/a/330131.html

相关文章:

  • epoll模型解析
  • 数据科学与计算:从基础到实践的全面探索
  • 深度学习(6):参数初始化
  • 动画相关 属性动画+animateToImmediately+ImageAnimator帧动画组件+模态转场
  • 【C++】哈希表的实现
  • EUDR的核心内容,EUDR认证的好处,EUDR意义
  • web开发,在线%射击比赛管理%系统开发demo,基于html,css,jquery,python,django,三层mysql数据库
  • lesson37:MySQL核心技术详解:约束、外键、权限管理与三大范式实践指南
  • SpringBoot工程妙用:不启动容器也能享受Fat Jar的便利
  • CAD 的 C# 开发中,对多段线(封闭多边形)内部的点进行 “一笔连线且不交叉、不出界
  • ECC的原理、背景、工作机制和数学基础
  • 升级Gradle版本后,安卓点击事件使用了SwitchCase的情况下,报错无法使用的解决方案
  • Query通过自注意力机制更新(如Transformer解码器的自回归生成)的理解
  • Unity3D 中纯 Shader 的双色纹理的平铺计算与实现
  • 二次筛法Quadratic Sieve因子分解法----C语言实现
  • [git diff] 对比检查变更 | 提交前复审 | 版本回退
  • SQL 核心操作全解析:从基础查询到关联关系实战
  • Spring Boot项目通过Feign调用三方接口的详细教程
  • 在es中安装kibana
  • 雨量系列篇一:翻斗雨量传感器与压电雨量传感器的区别是什么
  • java法定退休年龄计算器
  • Thinkphp(GUI)漏洞利用工具,支持各版本TP漏洞检测,命令执行,Getshell
  • reactive和ref使用方法及场景
  • GitHub 热榜项目 - 日榜(2025-08-13)
  • 光伏电站运维巡检指南
  • 02 流程流转
  • H616基于官方外设开发----1
  • 每日五个pyecharts可视化图表-line:从入门到精通 (5)
  • C++ 四种类型转换
  • el-table合并相同名称的列