一、多线程
1.进程和线程的区别
进程是系统运行的最小单位,一个java程序是一个进程,是互相独立的
线程是独立运行的最小单位,一个java程序可以有多个线程,线程之间可以共享数据
2.死锁的必要条件
互斥:同一资源同一时刻只能由一个程序读取
不可抢占:不能强行剥夺线程占有的资源
请求和保持:请求其他资源的时候对手中的资源保持不放
循环等待:互相等待的资源中,形成闭环
想要预防死锁,只需要破坏一个条件即可,例如使用定时锁
3.Synchronized和lock的区别
- Synchronized是关键字,lock是类
- syn会自动释放锁,lock需要手动释放
- syn是可重入锁、非公平锁、不可中断锁,lock的ReentrantLock是可重入锁,可以是公平锁也可以是非公平锁
- syn是jvm层面通过监视器实现的,lock是AQS实现的
4.什么是AQS锁
深入理解AQS队列同步器原理-从ReentrantLock的非公平独占锁实现来看AQS的原理
AQS是一个抽象类,可以用来构造所和同步类
原理
三个核心组件,一个是state代表加锁状态初始值为0,一个是获取到锁的线程,还有一个阻塞队列。当有线程想要获取锁的时候,会以CAS的形式将state变为1,CAS成功之后将加锁线程设为自己,当其他队列来竞争锁时会判断state是不是0,不是0再判断加锁线程是不是自己,不是自己就把自己放入阻塞队列,这个阻塞队列是双向链表实现的。
可重入锁的原理就是每次加锁时判断一下加锁线程是不是自己,是的话state+1,释放锁的时候就将state-1。当state减到0的时候就去唤醒阻塞队列的第一个线程。
5.slepp()和wait()的区别
- sleep是Thread的方法,wait是Object的方法
- sleep不会释放锁,wait会释放锁
- sleep没有限制,wait需要在同步方法或者同步代码块中执行
- sleep自动唤醒,wait需要调用notify或者notifyall唤醒
6.yield()和Join()的区别
yield调用后进入就绪状态
A线程调用B线程的join(),则B执行完前A进入阻塞状态
7.线程池七大参数
核心线程数:线程池中基本线程数量
最大线程数:当阻塞队列满了之后,逐一启动
最大线程的存活时间:当阻塞队列的任务执行完成后,最大线程的回收时间
最大线程的存活时间单位
阻塞队列:当核心线程满了后,后面来的任务进入阻塞队列
线程工厂:用于生产线程
任务拒绝策略:阻塞队列满了之后,拒绝任务的四种策略 (1)抛异常(2)丢弃任务不抛异常(3)打回任务(4)尝试与最老的线程竞争
8.JMM java内存模型
JMM屏蔽了各种硬件和操作系统的内存访问差异,实现java程序在各个平台下都能达到一致的内存访问效果,定义了JVM如何将程序的变量在主存中读取
定义:所有的变量都存在主存中,主存是线程共享区域;每个线程都有自己独立的工作内存,线程想要操作变量必须从主存中copy变量到自己的工作区,每个线程的工作内存都是互相隔离的
由于主存与工作内存之间有读写延迟,切读写不属于原子操作,所以会有线程安全问题
9.如何保证并发的三大特性
原子性:一次或多次操作在执行期间不能被其他线程影响
可以通过 synchronized和Lock实现原子性,volatile不能保证原子性。
可见性:当一个线程在工作内存修改了变量,其他线程应该立刻知道
volatile可以解决可见性问题(能否及时可见),不加volatile也可能可见,但不一定及时。synchronized和Lock也可以保证可见性,因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中。
有序性:JVM对指令的优化会让指令改变执行顺序,有序性是禁止指令重排序的
volatile可以保证可见性,禁止指令重排序(DCL需要添加volatile)。通过内存屏障实现。
10.ThreadLocal原理
为每个线程创建变量副本,不同线程之间不可见,保证线程的安全。每个线程内部维护一个Map,key作为threadloacl实例,value是保存的副本,使用threadlocal会造成内存泄漏,因为key是弱引用,value是强引用,每次gc的时候会回收key,value不会回收,可以每次使用完就删除value或者使用static修饰threadlocal解决内存泄露的问题
11.CAS锁和缺点
CAS可以保证原子性,思想是更新内存的时候判断是否被别人修改过,没有就直接更新。如果被修改了就要重新获取值,直到更新完成为止,缺点是
- 只能支持一个变量的原子操作,不能保证代码块的原子性
- CAS频繁导致CPU的开销大
- ABA问题:ab线程拿到a的值,a因为某种原因阻塞,b将值修改为其他值,又修改回来,如何成功退出,此刻a线程退出阻塞,查看值,发现还是之前的值,a没有发现值被修改了,这就是aba问题,可以通过增加版本号或者时间戳来解决
12.Synchronized锁原理和优化
syn是通过对象头的markwordk来表明监视器的,监视器本质是依赖操作系统的互斥锁实现的。操作系统实现线程切换需要从用户态转为内核态,成本很高,这种锁叫重量级锁,在JDK1.6之后引入了偏向锁、轻量级锁、重量级锁
偏向锁:当一个线程多次访问一段代码,都没有其他线程竞争,会获得偏向锁,偏向这段代码
轻量级锁:当锁是偏向锁的时候,有另外一个线程访问,会升级为轻量级锁,线程通过CAS获取锁,不会阻塞,提高性能
重量级锁:轻量级锁自旋一段时间还没有获取到锁,会升级为重量级锁,来竞争的线程都会阻塞,性能低下
注意:锁升级不是直接升级,需要经过自旋操作,只能升级不会降级
13. volatile 是什么?可以保证有序性吗?
一旦一个变量被volatile修饰,那么就具有两层语义
1)保证其他线程对这个操作的可见性,volatile修饰的变量被修改之后会被强制写入主存,其他线程立即可见
2)禁止指令重排序,局部有序性(前面的已经操作完成,后面的不能到volatiel前面来)
14.Thread 类中的start() 和 run() 方法有什么区别?
start()内部调用了run()方法,如果直接调用run那么还是单线程,调用start才是多线程