多线程
线程和进程的区别
进程是正在运行的程序的实例,进程中包含了线程,每个线程执行不同的任务
不同的进程使用不同的内存空间,在当前进程下所有线程可以共享内存空间
线程更加轻量,线程切换比进程切换开销低
并行和并发的区别
在多核cpu下 并发是同一时间应对多件事情,多个线程轮流使用一个或多个cpu
并行是同一时间动手做多件事情的,四核cpu同时执行四个线程
创建线程的方式
集成Thread类
实现runnable接口
实现Callable接口
线程池创建线程
runnable 和 callable区别
runnable接口run方法没有返回值
callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
run()和start()的区别
start:用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码,只能调用一次
run:封装了要被线程执行的代码,可以被多次调用
线程包含哪些状态,状态之间如何变化
怎么保证线程按顺序执行:使用join()方法 t2.join():等待t2线程执行结束后继续执行
notify()和notifyAll()的区别:
notifyAll:唤醒所有wait的线程
notify:只随即唤醒一个wait线程
wait和sleep方法的不同:
共同点:都能让当前线程暂时放弃CPU的使用权,进入阻塞状态
不同点:
方法归属不同:sleep是Thread的静态方法 wait是Object的成员方法,每个对象都有
醒来时机不同:
执行sleep和wait都会在等待相应毫秒后醒来
wait还可以被notify唤醒,如果不唤醒就一直等下去
两者都可以被打断唤醒
锁特性不同:
wait的调用必须先获取wait对象的锁,sleep无限制(wait和synchronized配合使用)
wait执行后会释放对象锁
sleep如果在synchronized代码块中执行,并不会释放对象锁
如何停止一个正在运行的线程:
使用退出标志,让线程正常退出
使用stop方法强行终止 不推荐
使用interrupt方法中断线程(打断阻塞的线程会抛出interruptedException异常)
synchronized关键字的底层原理
采用互斥的方法让同一时刻至多只有一个线程能够持有对象锁
底层由monitor实现,monitor是jvm级别的对象(C++实现),线程获得锁需要使用对象关联monitor
monitor内部有三个属性,分别是owner,entrylist,waitset
owner是关联的获得锁的线程,有且只有一个 entrylist关联的是处于阻塞状态的线程 waitset关联的是处于waiting状态的线程
monitor实现的锁属于重量级锁,了解过锁升级吗?
synchronized有偏向锁、轻量级锁、重量级锁三种 分别对应只有一个线程持有 不同线程交替持有 多线程竞争
重量级锁:使用monitor实现,涉及了用户态和内核态的切换,进程的上下文切换,成本高,性能低
轻量级锁:线程加锁的时间是错开的,可以使用轻量级锁来优化,修改了对象头的锁标志,相对于重量级锁性能提升。每次都是CAS操作,保证原子性
偏向锁:一段很长的时间都只被一个线程使用锁,可以使用偏向锁。在i第一次获得锁时,会有一个CAS操作,之后该线程再次获得锁时,只需要判断该mark word中是否是自己的线程id即可,不需要CAS操作
一旦发生了竞争,都会升级成重量级锁
JMM(java内存模型)
定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性
JMM把内存分为两块,一块是私有线程的工作区域,一块是所有线程的共享区域
线程和线程之间是相互隔离的,线程和线程交互需要通过主内存
CAS
全称是compare and swap 比较再交换,体现了一种乐观锁的思想,在无锁状态下保证线程操作数据的原子性
在操作共享变量的时候使用的自旋锁,效率更高
底层调用的是unsafe中的方法,都是操作系统提供的
乐观锁和悲观锁的区别
乐观锁(CAS):最乐观的估计,不怕别的线程来修改共享变量,如果修改了重新尝试
悲观锁(synchronized):最悲观的估计,进行上锁操作,本线程修改之后解开锁,其他线程才可执行。
对volatile的理解
保证线程间的可见性:用volatile修饰共享变量,能防止编译器等优化发生,让一个线程对共享变量的修改对两一个线程可见
禁止制令重排序:用volatile修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果
什么是AQS(AbstractQueuedSynchronizer 抽象队列同步器)
ReentrantLock的实现原理
表示支持重新进入的锁,调用lock方法获取了锁之后,再次调用lock,不会阻塞
主要利用CAS+AQS队列实现
支持公平锁和非公平锁 构造器中无参默认是非公平锁
死锁产生的原因:
一个线程需要同时获取多把锁,容易发生死锁
如何进行死锁诊断
jdk自带:
jps:输出jvm中运行的进程状态信息
jstack:查看java进程内线程的堆栈信息 jstack -l
其他
jconsole:对jvm的内存,线程,类的监控
visualVM:故障处理工具
ConcurrentHashMap
底层数据结构:
JDK1.7 分段的数组+链表
JDK1.8 数组+链表/红黑二叉树
加锁的方式:
1.7采用segment分段锁,底层使用ReentrantLock
1.8采用CAS添加新节点,采用synchronized锁定链表或者红黑树的首节点,性能更好
导致并发程序出现问题的根本原因是什么(Java中怎么保证多线程的执行安全)
java并发编程三大特征:解决方式
原子性:synchronized lock
内存可见性:volatile synchronized
有序性:volatile
线程池的核心参数
线程池的执行原理
线程池中常见的阻塞队列
ArrayBlockingQueue:基于数组结构的有界阻塞队列 FIFO
LinkedBlockingQueue:基于链表结构的有界阻塞队列 FIFO
DelayedWorkQueue:是一个优先队列,可以保证每次出队的任务都是当前队列中执行时间最靠前的
SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作
如何设置核心线程数
高并发、任务执行时间短(CPU核数+1)
并发不高,任务执行时间长
io密集型(CPU*2+1)
计算密集型(CPU+1)
并发高,业务执行时间长:关键不在线程池而在架构的设计,可以给数据做缓存,增加服务器
线程池的种类:
newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
newSingleThreadExecutor:创建一个单线程化的线程池,他只会唯一的工作线程来执行任务,保证所有任务按照指定顺序执行(FIFO)
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收线程,则新建线程
newScheduledThreadPool:可以执行定时任务的线程池,支持定时及周期任务执行
为什么不建议使用Executors创建线程池
FixedThreadPool和SingleThreadPool,允许的请求队列长度位integer的最大值,会堆积大量的请求,导致OOM
CachedThreadPool允许的创建线程为integer的最大值,会创建大量的线程,导致OOM
如何控制某个方法的允许并发访问线程的数量
多线程提供的工具类Semaphore,信号量。控制方法的访问量
创建对象,acquire请求信号量,release释放信号量
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
2023-09-27 9.27每日总结
2023-09-27 9.26每日总结
2023-09-27 《架构师之路:软件架构之美》第二章读书笔记